Java/EffectiveJava

Effective Java Item8. finalizer와 cleaner 사용을 피하라

Flambee 2025. 1. 12. 00:08

자바는 finalizer와 cleaner 두 가지 객체 소멸자를 제공한다.

  • finalizer : 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다.
  • cleaner : finalizer보다는 더 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.
    • finalizer와 cleaner는 하나의 자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할이다.
      • FileInputStream, FileOutputStream, ThreadPoolExecutro가 대표적이다.
  • cleaner와 finalizer는 안정망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자. 물론 이런 경우라도 불확실성과 성능 저하에 주의해야한다.
  • 네이티브 피어(native peer)와 연결된 객체에서 활용한다.
    • 네이티브 피어는 자바 객체가 아니니 가비지 컬렉터는 그 존재를 알지 못한다. 그 결과 자바 피어를 회수할 때 네이티브 객체까지 회수하지 못한다.
    • 네이티브 피어 객체 회수는 cleaner와 finalizer가 나서서 처리하기에 적당한 작업이다. 단, 성능 저하를 감당할 수있고 네이티브 피어가 심각한 자원을 가지고 있지 않을 떄에만 해당된다.
    • 성능 저하를 감당할 수 없거나 네이티브 피어가 사용하는 자원을 즉시 회수해야 한다면 앞서 설명한 close 메서드를 사용해야 한다.

네이티브 피어(native peer)란?
일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 말한다.
네이티브 피어는 자바 객체가 아니다.

 

finalizer와 cleaner는 즉시 수행된다는 보장이 없다.

  • 객체에 접근할 수 없게 된 후 finalizer나 cleaner가 실행되기까지 얼마나 걸릴지 알 수 없다. 즉, finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다.

자바 언어 명세는 finalizer나 cleaner의 수행 시점뿐 아니라 수행 여부조차 보장하지 않는다.

  • 접근할 수 없는 일부 객체에 딸린 종료 작업을 전혀 수행하지 못한 채 프로그램이 중단될 수도 있다.
  • 즉, 상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존해서는 안 된다.

finalizer 동작 중 발생한 예외는 무시될 수 있다.

  • 처리할 작업이 남았더라도 그 순간 종료된다. 잡지 못한 예외 때문에 해당 객체는 자칫 마무리가 덜 된 상태로 남을 수 있다.
  • 다른 스레드가 이처럼 훼손된 객체를 사용하려 한다면 어떻게 동작할지 예측할 수 없다.
  • finalizer에서 예외가 발생하면 경고조차 출력하지 않는다. 하지만 cleaner는 자신의 스레드를 통제하기 때문에 문제가 발생하지 않는다.

finalizer와 cleaner는 성능 문제도 동반한다.

  • AutoClosable과 비교했을 때 50배의 gc 성능 차이가 있다.

finalizer를 사용한 클래스는 finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수 있다.

finalizer와 cleaner를 사용하는 파일이나 스레드 등 종료해야 할 자원을 담고 있는 객체의 클래스에서 AutoCloseable을 구현하고, 클라이언트에서 인스턴스를 다 쓰고나면 cloase 메서드를 호출할 수 있도록 구현한다.