8. finalizers와 cleaners 피하기
파이널라이저(finalizer)
- finalize() 메소드
- 클래스의 객체가 더 이상 사용되지 않으면 JVM의 가비지 컬렉터가 메모리에서 객체를 없애기 전에 finalizer() 호출해줌
- 가비지 컬렉터는 new 키워드로 생성된 객체의 메모리만을 클린업해줄 수 있음
- 예측이 안되기 때문에 조심히 사용해야 하고, 성능 저하 및 이식성 문제가 생길 수 있음
- 자바에서는 사용할 수 없는 객체와 관련된 메모리를 가비지 컬렉터가 자동으로 재활용
- javap9에서 deprecated되었지만 여전히 라이브러리에서 사용된다 finalizer대신 cleaners를 사용한다.
- cleaner는 덜 위험하지만 여전히 예측불가능하고 느리고 일반적으로 필요없다.
- C++ 소멸자는 자바의 파이널라이저가 아니다!!!!
- 객체와 관련된 자원을 재활용하는 정상적인 방법
- 생성자의 필수적인 짝
- 메모리가 아닌 다른 자원을 재활용하는데 사용 (자바에서는 try-finally 블록으로 사용)
파이널라이저의 단점
- 신속하게 실행된다는 보장이 없음
- 객체가 사용할 수 없게 되는 시점부터 파이널라이저가 실행되는 시점까지 긴 시간이 소요될 수 있음
- 파이널라이저 내부에서는 실행 시간이 매우 중요한 작업을 절대 하지 말아야 함
- 예) 파이널라이저에서 파일 닫는 것은 절대 하면 안됨, 파일이 언제 닫힐지 보장이 안됨
- 파이널라이저가 얼마나 빨리 실행되는가는 가비지 컬렉션 알고리즘에 달려있고, 이것은 JVM 종류에 따라 다양
- 반드시 실행되는지도 보장하지 않음
- 더 이상 사용할 수 없는 객체에 대해 파이널라이저가 실행되지 않은 채로 프로그램 종료될 수 있음
- 파이널라이즈하는 동안 catch되지 않은 예외가 발생하면 해당 예외는 무시됨
- catch되지 않은 예외가 발생하면 스레드 실행이 중단되고 스택 추적 정보(stack trace)가 출력되는데 파이널라이저 안에서 발생하면 그런 예외도 무시되기 때문에 경고도 출력되지 않음
- 엄청난 성능 저하
- 파이널라이저를 사용해서 객체를 생성하고 소멸시키면 약 430배 느려짐
-
- 종결작업이 필요한 자원을 가지는 객체는 파이널라이저 대신 무엇을 써야 할까?
- 작업이나 자원을 정상적으로 종료하는 메소드만 별도로 추가해주면 됨
- 더 이상 필요없는 각 인스턴스에 대해서 종료 메소드를 호출하도록 함
- 각 인스턴스에서 자신의 종료 여부를 유지 관리해야 함 종료 메소드에서 해당 객체가 더 이상 유효하지 않다는 것을 private 필드에 기록하고, 다른 메소드에서는 이 필드값을 확인해서 해당 객체가 종료된 이후에 호출되면 IllegalStateException 예외 발생
- : 종료 메소드 예
- InputStream, OutputStream, java.sql.Connection에 있는 close 메소드
- java.util.Timer의 cancel 메소드
- http://docs.oracle.com/javase/6/docs/api/java/util/Timer.html#cancel()
- Timer 인스턴스와 연관된 스레드가 자신을 정상적으로 종료하는데 필요한 상태 변경
-
가급적 종료 메소드는 try-finally와 함께 사용해서 확실하게 실행되도록! finally에서 종료 메소드를 호출하면 해당 객체를 사용중에 예외가 생기더라도 항상 실행됨
1: //try-finally 블록을 사용하면 종료 메소드의 실행이 보장 2: Foo foo = new Foo(); 3: try{ 4: //foo 객체를 사용해서 해야할일을 처리하는 코드 5: //… 6: }finally{ 7: foo.terminate(); //종료 메소드 호출 8: }
-
- 파이널라이저의 적합한 용도
- 1) 생성된 객체를 갖고 있는 코드에서 그 객체의 종료 메소드 호출을 빠트렸을 경우에 안전망의 역할
- 클라이언트가 종료 메소드 호출에 실패하는 경우를 대비해서 파이널라이저 사용 자원의 사용이 완전히 끝나지 않은 경우에는 파이널라이저에서 경고 메시지로 기록해야 함 FileInputStream, FileOutputStream, Timer, Connection은 파이널라이저를 가지고 있음, 종료 메소드가 호출되지 않을 경우 안전망의 역할 이들 파이널라이저에서는 경고메시지를 기록하지 않는다.
2) 네이티브 피어(native peer) 객체 네이티브 메소드(자바 외의 다른 프로그래밍 언어)를 통해 일반 자바 객체가 자신의 일을 위임하는 네이티브 객체 일반 자바 객체가 아니기 때문에 자바 피어 객체가 소멸되면 가비지 컬렉터가 알지 못하고 재활용 불가 그렇기 때문에 파이널라이저가 적합한 수단 네이티브 피어가 빠른 시간 내에 종결되어야 하는 자원이라면 종료 메소드를 별도로 가지고 있어야 함
-
파이널라이저의 연쇄 호출(chaining)은 자동으로 실행되지 않음 연쇄 호출 : 상속 관계가 있는 클래스에서 서브 클래스의 메소드 호출 시 이와 관련된 수퍼 클래스의 메소드가 자동 호출되는 것 클래스가 파이널라이저 메소드를 가지고 있고 서브 클래스에서 그 메소드를 오버라이드(override)하면 서브 클래스의 파이널라이저에서 수퍼 클래스의 파이널라이저를 반드시 호출해야 함 try 블록에서 서브 클래스를 파이널 라이즈하고, try의 finalliy 블록에서 수퍼 클래스의 파이널라이저 메소드 호출 수퍼 클래스의 파이널라이저 메소드 호출을 안하면 수퍼 클래스의 파이널라이저는 절대 실행되지 않음
1: //파이널라이저의 연쇄 호출은 우리가 직접 해야 한다 2: protected void finalize() throws Throwable { 3: try{ 4: //해당 클래스를 파이널라이즈 하는 코드들 5: }finally{ 6: super.finalize(); //수퍼 클래스의 파이널라이저 호출 7: } 8: }
- -파이널라이저 가디언(finalizer guardian)
- 모든 객체가 파이널라이즈 되도록 추가 객체 생성
- 파이널라이즈가 필요한 클래스의 파이널라이저를 작성하는 대신 자신을 포함하는 외부 클래스의 인스턴스를 파이널라이즈 하는 목적만을 갖는 익명의 내부 클래스에 파이널라이저를 만드는 것 :파이널라이즈하는 목적만을 가지는 파이널라이저를 만드는 것
- 자신을 포함하는 외부 클래스의 각 인스턴스당 하나씩 생성
- 외부 클래스 인스턴스는 자신의 파이널라이저 가디언 객체 참조를 private 인스턴스 필드에 보존
- 가디언의 파이널라이저는 마치 외부 클래스 메소드인 것처럼 외부 클래스 인스턴스에 필요한 파이널라이즈 작업 수행
1: public class Foo { 2: //목적은 외부 클래스(Foo) 객체의 파이널라이즈를 수행하는 것 3: private final Object finalizerGuardian = new Object(){ 4: protected void finalize() throws Throwable{ 5: //외부 클래스 (Foo) 객체의 파이널라이즈를 수행하는 코드 6: } 7: }; 8: //… 9: //… 10: }
Comments