[Effective Java] 아이템 8 : finalizer와 cleaner 사용을 피하라

2022. 3. 30. 10:41Java/Effective Java

728x90
반응형


자바의 객체 소멸자

  1. Finalizer : 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요, 그냥 쓰지 마세요.. 그냥 쓰지 말라고
  2. Cleaner : Finalizer 보다는 덜 위험하지만, 여저힌 위험하고, 불필요하다.

C++의 destructor와 Java의 finalizer & cleaner

파괴자는 특정 객체와 관련된 자원을 회수하는 보편적인 방법이고 자바의 소멸자들은 접근할 수 없게 된 객체를 회수하는 역할을 가비지 컬렉터가 담당한다.

자바에서는 try-with-resources와 try-finally를 사용해 해결한다.


왜 Finalizer와 Cleaner의 사용을 자제해야하나?

  1. Finalizer와 Cleaner는 즉시 수행된다는 보장이 전혀 없다.
    • 위 두 가지로는 데때 실행되야 하는 작업은 절대 할 수 없다. 파일 닫기를 맡긴다면 중대한 오류를 야기할 수 있고, 동시에 실행 가능한 개수에 한계가 있기 때문이다.
    • 위 두 가지의 속도는 전적으로 가비지 컬렉터가 담당하고 있다.
    • 가비지 컬렉터도 어떻게 코드를 짜냐에 따라 속도가 천차만별이다.
  2. 수행 시점과 수행 여부조차 보장할 수 없다.
    • 접근 할 수 없는 객체에 딸린 종료 작업을 전혀 수행하지 못한 채 프로그램이 중단될 가능성이 있다.
    • 영구적으로 지속되어야 하는 작업에서는 절대 두 가지 소멸자를 사용하면 안된다.
    • Finalizer는 예외는 무시되며, 처리할 작업이 남았더라도 순간 종료된다. 마무리가 덜 된 상태에 놓이게 된다.
    • 이런 일이라도 Finalizer는 경고 조차 출력하지 않지만, Cleaner는 자신의 스레드를 통제하기 때문에 이런 일이 없다.
  3. 심각한 성능 문제도 동반한다.
    • AutoCloseable 객체보다 약 50배나 느리게 작동한다. 왜냐하면 가비지 컬렉터의 성능을 저 두 소멸자가 떨어뜨리기 때문이다.
  4. Finalizer를 사용한 클래스는 공격에 노출될 수 있다.
    • 생성자나 직렬화 과정에서 예외가 발생되면, 악의적인 하위 클래스의 finalizer가 수행될 수 있게 된다. 이는 정적 필드에 자신의 참조를 할당하여 가비지 컬렉터의 수집을 막게되는 현상이다.
    • 이를 막기위해 아무 일도 하지 않는 Finalize 메서드를 만들고 Final로 선언하자.

대안책은?

AutoCloseable을 구현하, 다 쓴 인스턴스는 close 메소드를 호출하자
만약 Cleaner를 사용해야 한다면 IllegalStateException 예외를 던져주자


쓰면 안되면 어디에 써야 하는 소멸자들인가?

  • 자원의 소유자가 close 메소드를 호출하지 않는 것에 대비한 안전망
  • 네이티브 피어와 연결된 객체인데 네이티브 피어는 가비지 컬렉터와 연결되지 않았기 때문이다. 이를 회수하기 위해서는 close 메소드를 사용하자.

예제

import java.lang.ref.Cleaner;  
  
public class Room implements AutoCloseable {  
  
      private static final Cleaner cleaner = Cleaner.create();  
      
      private static class State implements Runnable {  
      int numJunkpiles;  
      
      State(int numJunkpiles) {  
	      this.numJunkpiles = numJunkpiles;  
      }  
      
      @Override  
      public void run() {  
	      System.out.println("방 청소");  
	      numJunkpiles = 0;  
      }  
     }  
     private final State state;  
     private final Cleaner.Cleanable cleanable;  
      
     public Room(int numJunkpiles) {  
      state = new State(numJunkpiles);  
      cleanable = cleaner.register(this, state);  
      }  
      
      @Override  
      public void close() throws Exception {  
      cleanable.clean();  
      }  
}

책의 예제이다.

 

이 예제 코드에서는 절대 State 클래스가 Room을 참조하면 안된다.

public calss Adult {
    public static vodi main(String[] args) {
        try (Room myRoom = new Room(7)) {
            System.out.println("안녕~");
        }
    }
}

결과로 ''안녕~''이 출력된다

public calss Teenager {
    public static vodi main(String[] args) {
        new Room(99);
        System.out.println("아무렴");
    }
}

결과로 방 청소가 먼저 출력되고 아무렴이 출력된다. Room 메소드 안에 보면 state를 선언할 때 run() 메소드에서 방 청소를 먼저 실행하기에 가능한 일이다.

728x90
반응형