본문 바로가기

자바

(이펙티브 자바) 아이템 08. finalizer와 cleaner 사용을 피하라

이번 아이템은 제목에서 유추할 수 있듯이 finalizer와 cleaner의 사용을  피하는 것을 말하고 있다.
피해야 되는 이유에 대해서 크게 CS, 성능, 보안 측면으로 소개하고 있다. 또한, 아래와 같이 자바 9 이후 finalize 메서드는 deprecated 되었음을 볼 수 있다.

그리고 마지막으로 언제 사용해야 하는지에 대한 구체적인 정보를 알려준다.

자바 9버전 이후부터 deprecated 되었다.


단점 1. Finalizer는 실행이 아예 안될 수 도 있고 실행이 된다 해도 인스턴스 반납의 지연을 일으킬 수 있다.

아래는 Object 클래스에서 재정의한 finalize 메서드와 hello클래스를 예시로 들었다.

/**
 * Project : FourTeamEffectiveJavaStudy
 *
 * @author : jwdeveloper
 * @comment :
 * Time : 8:56 오후
 */
public class FinalizerExample {

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Clean Up");
    }

    public void hello() {
        System.out.println("hola~");
    }
}

아래는 실행 부이다.

/**
 * Project : FourTeamEffectiveJavaStudy
 *
 * @author : jwdeveloper
 * @comment :
 * Time : 9:00 오후
 */
public class SampleRunner {
    public static void main(String[] args) throws InterruptedException {
        SampleRunner sampleRunner = new SampleRunner();
        sampleRunner.run();
        Thread.sleep(1000l);
    }

    private void run() {
        FinalizerExample finalizerExample = new FinalizerExample();
        finalizerExample.hello();
    }
}

실행 결과

호출되지 않은 이유는 Finalizer의 Thread 우선순위가 낮기 때문이다. 그러므로 언제 호출될지 모르는 것이며 해당 인스턴스의 자원 반납이 늦춰지고 최악으로는 OOM이 일어날 수 있다. 

https://jwdeveloper.tistory.com/221?category=836504

 

(운영체제) DeadLock & Starvation

DeakLock이란? 두 개 이상의 작업이 서로 상대방의 작업이 끝나기만을 기다리고 있기 때문에 다음 단계로 진행하지 못하는 상태 a, b라는 임계 자원이 있다고 가정하자 ThreadA에서는 a 임계 자원을 ��

jwdeveloper.tistory.com


2. 보안상의 이슈

만약 finalizer를 사용한 클래스가 있다면 심각한 보안 문제를 일을 킬 수 있다.

 

어떠한 인스턴스 생성과정에서 예외가 발생한다고 가정하면 이 생성되다만 인스턴스의 하위 클래스의 finalizer가 수행될 수도 있기 때문이다.

 

해당 하위 클래스의 finalizer()는 상위 클래스의 정적 필드 혹은 메서드에 접근해서 GC의 대상이 안되게 만들어 OOM을 발생하게 할 수 있고 더 나아가 메서드나 필드에 대한 정보를 외부로 노출시킬 수도 있다.

/**
 * Project : FourTeamEffectiveJavaStudy
 *
 * @author : jwdeveloper
 * @comment :
 * Time : 9:26 오후
 */
public class A {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
    }
}
class B extends A{
    @Override
    protected void finalize() throws Throwable {
        /*
        * Class A에 대한 정보를 외부에 노출시킬 수 있고
        * OOM을 일으킬 수 도 있다.
        * */
        super.finalize();
    }
}

그럼 언제 사용해야 하는가?

1. try catch resource

언제 사용되는가를 알려면 먼저 try catch resource에 대해서 알아야 한다.

 

아래와 같이 AutoClosable 인터페이스를 구현하는 Resouce 클래스를 두어 자원을 반납한다.

/**
 * Project : FourTeamEffectiveJavaStudy
 *
 * @author : jwdeveloper
 * @comment :
 * Time : 9:34 오후
 */
public class SampleResource implements AutoCloseable{
    @Override
    public void close() throws Exception {
        System.out.println("close");
    }

    public void hello() {
        System.out.println("hello");
    }
}

클라이언트는 예외 처리를 하여 꼭 close를 호출하여 자원을 반납해야 한다.

/**
 * Project : FourTeamEffectiveJavaStudy
 *
 * @author : jwdeveloper
 * @comment :
 * Time : 9:35 오후
 */
public class SampleRunner2 {
    public static void main(String[] args) throws Exception {

        SampleResource sampleResource = null;

        try {
            sampleResource = new SampleResource();
            sampleResource.hello();
        } finally {
            if (sampleResource != null) {
                sampleResource.close();
            }
        }
    }
}

이 책에서 가장 이상적인 코드 구조는 try catch resource로 자원을 반납하는 것이라고 한다.

이렇게 하면 명시적으로 close 메서드를 호출하지 않아도 try 블록이 끝날 때 알아서 close가 호출된다.

/**
 * Project : FourTeamEffectiveJavaStudy
 *
 * @author : jwdeveloper
 * @comment :
 * Time : 9:35 오후
 */
public class SampleRunner2 {
    public static void main(String[] args) throws Exception {

        try(SampleResource sampleResource1 = new SampleResource()) {
            sampleResource1.hello();
        }
    }
}

자바 10부터는 var로 사용하능!

try(var sampleResource1 = new SampleResource()) {
    sampleResource1.hello();
}

 

그러면 만약 클라이언트 측에서 위와 같이 명시적으로든 암묵적으로든 close를 사용하지 않았다면?

 

그때 finalize를 사용하는 것이다.

/**
 * Project : FourTeamEffectiveJavaStudy
 *
 * @author : jwdeveloper
 * @comment :
 * Time : 9:34 오후
 */
public class SampleResource implements AutoCloseable{

    private boolean closed;

    @Override
    public void close() throws RuntimeException {
        if (this.closed) {
            throw new IllegalStateException();

        }
        System.out.println("close");
    }

    public void hello() {
        System.out.println("hello");
    }

    @Override
    protected void finalize() throws Throwable {
        if (!this.closed)
            close();
    }
}

2. 네이티브 피어 정리 

네이티브 객체는 일반적인 객체가 아니기에 GC의 대상이 아니다. 

그러므로 네이티브 피어가 들고 있는 리소스가 중요하지 않는 자원이라면 혹은 성능상의 이슈가 크지않은 자원이라면 Cleaner와 Finalizer를 사용해서 해당 자원을 반납할 수 있다.

  • 중요 리소스인 경우에는 close() 메서드를 사용하자!