Effective Java - Item 8

Updated:

Effective Java

Item 8. finalizer와 cleaner 사용을 피하라

자바의 두 가지 객체 소멸자

finalizer

@Override
public void finalize() {
    // ...
}

finalize 메서드를 Override하면 해당 객체가 JVM에게 GC를 해야 할 대상이 될 때 호출된다.
객체가 없어지기 전 다른 연관 자원을 정리하려는 의도로 작성된다.

하지만 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다.
따라서 자바 9에서 부터 deprecated API로 지정되어 사용을 지양한다.

cleaner

public class CleaningExample implements AutoCloseable {
  // Cleaner. 클라이언트가 close를 호출하지 않는다면, cleaner가 State의 run 메서드를 호출한다.
  private static final Cleaner cleaner = <cleaner>;

  // 절대 CleaningExample를 참조해서는 안된다!
  static class State implements Runnable {

    State(...) {
      // 클린 작업을 수행하기 위해 필요한 State를 초기화 한다.
    }

    public void run() {
      // State에 접근하여 cleanup 작업 수행, 단 한번만 수행한다.
      System.out.println("clean");
    }
  }

  private final State;
  private final Cleaner.Cleanable cleanable

  public CleaningExample() {    // 생성자
    this.state = new State(...);
    this.cleanable = cleaner.register(this, state);
  }

  // finalizer나 cleaner를 대신할 소멸자.  
  public void close() {
    cleanable.clean();
  }
}

finalizer의 대안. Java9 에서 도입된 소멸자로 보통, AutoCloseable을 구현해서 try-with-resource 와 같이 사용한다.
close 메서드가 호출되지 않은 경우, Cleaner에서 cleaning 작업을 호출한다. 즉, close 메서드를 호출하지 않는 것에 대비한 안정망 역할 이다.

public Class MainTest{
  public static void main(String[] args){
    try(CleaningExample ce = new CleaningExample(5)){
      System.out.println("hi");
    }
  }
}

try-with-resources와 clean을 사용한 클래스의 사용 예.
실행 후, “clean”을 출력한다.

사용하지 않는 이유

  1. 수행된다는 보장이 없다.
    즉, 위 두 소멸자로는 제때 실행되어야 하는 작업은 절대 할 수 없다.
    finalizer나 clenaer를 얼마나 신속하게 수행할지는 GC에 달려있고, 다른 스레드보다 우선 수위가 낮아 수행 되지 않을 가능성도 높다.

상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존해서는 안된다.
예를 들어 DB와 같은 공유 자원의 영구 lock 해제를 finalizer나 clenaer에 맡기면, 분산 시스템이 서서히 멈출 것이다.

  1. 동작 중 발생한 예외는 무시되어, 처리할 작업을 마저 수행하지 않는다. 잡지 못한 예의 때문에 객체는 자칫 마무리가 덜 된 상태로 남을 수 있다.
    따라서, 훼손된 객체를 다른 스레드가 사용하려 할 수 있다.

  2. 심각한 성능 문제를 동반한다.
    AutoCloseable 객체를 생성하고 GC가 수거하기 까지 (객체 생성, 정리, 파괴)
    • Try-catch-resources : 12ns
    • finalizer : 550ns
    • 안정망 방식 + finalizer : 66ns
  3. 심각한 보안문제를 일으킬 수 있다.
    finalizer를 사용한 클래스는 finalizer 공격에 취약하다.
    객체를 생성 중에서 예외가 발생하면 생성되다만 객체의 악의 적인 하위 클래스의 finalizer가 수행될 수 있다.
    finalizer는 GC가 수집하지 못하게 막을 수 있으며, 이 객체의 메서드를 호출해 애초에 허용되지 않았을 작업을 수행 한다.

사용하는 예

  1. try-with-resource와 같이 쓰이며, close 메서드 미호출에 대비한 안정망
  2. Native peer와 연결된 객체의 자원을 회수
    [네이티브 피어]는 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 객체.
    Native peer는 GC가 존재를 알지 못함. 따라서 cleaner나 finalizer가 나서서 처리하기에 적당한 작업.

정리

cleaner는 안정만 역할이나 중요하지 않는 네이티브 자원 회수용으로만 사용하자.
물론 이런 경우라도 불확실성과 성능 저하에 주의해야 한다.

출처

Joshua Bloch. Effective Java 3/E. n.p.: 인사이트, 2018년 11월 1일.

참고 페이지

https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html https://blog.javarouka.me/2018/11/26/Finalizer%EC%99%80-Cleaner/