2011. 7. 19. 09:23

java.lang.Object 메쏘드 분석 5 - finalize

자바의 메모리 해제는 Garbage Collector(이하 GC)에 의해 수행됩니다. finalize()는 GC에 의해 호출됩니다. 

java.lang.Object에 선언된 메쏘드 선언부를 살펴보겠습니다.

protected void finalize() throws Throwable { }

일단 protected입니다. 하위 클래스에서 오버라이드할 수 있는 여지를 남겨두었습니다. 
throws Throwable() 부분도 눈 여겨봐야 합니다. 무슨 에러가 나면 바로 던져 버리겠다는 뜻입니다. finalize를 호출하는 것은 GC입니다. 어떤 exception이든 GC는 걍 버립니다. 따라서 finalize 실행 중 발생한 에러는 try-catch 로 감싸지 않는 한 처리할 방법이 없습니다.

finalize()는 실행된다는 보장이 없습니다. 실행이 될 수도 안 될 수도 있고 머 그렇습니다. 그래서 필수적인 로직을 포함시켜서는 안 됩니다. 

예제를 한 번 봅시다.

package objectMethod.finalize;
public class AnObject {
    @Override
    protected void finalize() throws Throwable{
            System.out.println("out!!");
            new Object();
    }
}

package objectMethod.finalize;

public class Test {
    public static void main(String[] args) {
        Test test = new Test();
        test.foo();
        System.gc();
    }
    public void foo(){
        AnObject obj = new AnObject();  
    }
}

Test.foo() 에서 AnObject의 객체가 생성됩니다. 이는 System.gc()가 호출되면, GC에 의해 처리가 됩니다. 그러므로 finalize()가 호출이 됩니다. 제 컴터의 java (sun 1.5.12) 에서는 System.gc() 부분을 지우면 finalize()가 호출되지 않더군요.

jsr-133 자바 메모리 모델의 16장에 Finalizer에 대해서 자세히 나와있습니다. finalize()는 생성자와는 달리 상위 클래스의 finalize()가 자동으로 호출되지 않습니다. 따라서

protected void finalize() throws Throwable {
    super.finalize();
    // 기타 추가 코드
}

와 같은 방법으로 명시적인 상위클래스의 finalize()를 호출해야 합니다.

언제나 그렇듯.. 이런 식으로 명식적으로 호출하는 것이 번거롭습니다. 그럴 때는 Finalizer Guardian이라는 방법을 쓰면 됩니다.(역시 jsr-133에 나옵니다.)

class Foo {
    private final Object finalizerGuardian = new Object() {
        protected void finalize() throws Throwable {
            /* finalize outer Foo object */
        }
    }
}

Foo를 상속 받은 클래스의 인스턴스가 생기면 상위 클래스인 Foo의 객체도 생기고 하위 클래스의 인스턴스가 GC한테 제거 당할 때 상위클래스의 인스턴스도 같이 제거당하는데, 그때 또 상위 클래스의 인스턴스 변수인 finlaizerGuardinal이란 멤버 변수도 제거 대상이 되고, 여기서 finalize()가 호출됩니다.

finalize는 반드시 호출된다는 보장이 없기 때문에 필수적인 로직을 넣으면 안 됩니다. 파일 스트림이나 디비 커넥션 등을 열었을 때는 명시적으로 닫아주는 것이 좋습니다. finalize()를 이용하면 안 됩니다.

그럼 finalize()는 모하는데 쓰느냐? 혹시나 하는 로직을 넣으면 됩니다. 디비를 열고 사용자가 혹시 안 닫았으면 닫는 로직정도죠.

if(!connection.isClosed()){
    connection.close();
}

위의 예제 정도가 적당하겠습니다.