자바는 두 가지 객체 소멸자를 제공한다. 그중 finalizer는 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다.
cleaner 는 finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고 ,일반적으로 불필요하다.
두 가지 경우에 사용한다.
자바9에서는 Finalizer가 deprecated 됐으며 Cleaner라는 게 새로 생겼다.
언제실행될지 알 수 없다.어떤 객체가 더이상 필요 없어진 시점에 그 즉시 finalizer 또는 cleaner가 바로 실행되지 않을 수도 있다. 따라서 타이밍이 중요한 작업을 절대로 finalizer나 cleaner에서 하면 안된다.
ex) 파일 닫기를 finalizer 나 cleaner에 맡기면 중대한 오류를 일으킬 수 있다. 시스템이 동시에 열수 있는 파일 개수에 한계가 있기 때문이다.그래서 실행을 게을리해서 파일을 계속 열어둔다면 새로운 파일을 열지못해 프로그램이 실패할 수 있다.
인스턴스의 자원회수가 제멋대로 지연될 수 있다. Finalizer 쓰레드는 우선 순위가 낮아서 언제 실행될지 모른다. 따라서 Finalizer안에 어떤 작업이 있고, 그 작업을 쓰레드가 처리 못해서 대기하고 있다면 해당 인스턴스는 GC가 되지 않고 계속 쌓이다가 결국엔 OutOfMemoeryException이 발생할 수 있다.
한편 cleaner는 자신을 수행할 스레드를 제어할 수 있다는 면에서 조금 낫지만 여전히 백그라운드에서 수행되며 가비지 컬렉터의 통제하에 있으니 즉각 수행되리라는 보장은 없다.
수행 시점뿐 아니라 수행여부조차 보장하지 않는다.
상태를 영구적으로 수정하는 작업에서는 절대 finalizer 나 cleaner에 의존해서는 안된다. 데이터베이스 같은 자원의 락을 그것들로 반환하는 작업을 한다면 전체 분산 시스템이 멈출 수 있다.
System.gc나 System.runFinalization에 속지 마라. 그걸 실행해도 바로 실행한다고 보장되지 못한다.
심각한 성능 문제도 동반된다. AutoCloseable 객체를 만들고 try-with-resource로 자원 반납을 하는데 걸리는 시간은 12ns인데 반해 finalizer을 사용한 경우에 550ns가 걸렸다. cleaner는 66ns가 걸린다.
finalizer를 사용한 클래스는 finalizer 공격에 노출되어 심각한 보안 문제가 일으킬 수 있다.
공격 원리는 간단하다. 생성자나 직렬화 과정에서 예외가 발생하면, 이 생성되다만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있게 된다. 이 finalizer는 정적 필드에 자신의 참조를 할당하여 가비지 컬렉터가 수집하지 못하게 막을 수 있다.
객체 생성을 막으려면 생성자에서 예외를 던지는 것만으로 충분하지만, finalizer가 있다면 그렇지도 않다.
final 클래스는 상속이 안되니까 근본적으로 이런 공격이 막혀 있으며, 다른 클래스는 finalize()메소드에 final 키워드를 사용해서 상속해서 오버라이딩 하는것을 막을 수 있다.
하나의 자원의 소유자가 close메소드를 호출하지 않는 것에 대비한 안전망 역할
즉시 호출되리라는 보장은 없지만, 클라이언트가 하지 않은 자원 회수를 늦게라도 해주는 것이 아예 안하는 것보다 낫다.
ex) FileInputStream, FileOutputStream, ThreadPoolExecutor
네이티브 피어와 연결된 객체에서
네이티브 피어란 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 말한다.
자바 클래스 -> 네이티브 메소드 호출 -> 네이티브 객체 (네티이브 Peer)
네이비트 피어는 자바 객체가 아니니 가비지 컬렉터는 그 존재를 알지 못한다. 그 결과 자바 피어를 회수할때 네이티브 객체까지 회수하지 못한다. cleaner나 finalizer를 사용해서 처리하기에 적당한 작업이다. 단 성능 저하를 감당할 수 있고 네이티브 피어가 심각한 자원을 가지고 있지 않을 때에만 해당된다.
// 코드 8-1 cleaner를 안전망으로 활용하는 AutoCloseable 클래스 (44쪽)
public class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다!
private static class State implements Runnable {
int numJunkPiles; // Number of junk piles in this room
State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}
// close 메서드나 cleaner가 호출한다.
@Override public void run() {
System.out.println("Cleaning room");
numJunkPiles = 0;
}
}
// 방의 상태. cleanable과 공유한다.
private final State state;
// cleanable 객체. 수거 대상이 되면 방을 청소한다.
private final Cleaner.Cleanable cleanable;
public Room(int numJunkPiles) {
state = new State(numJunkPiles);
cleanable = cleaner.register(this, state);
}
@Override public void close() {
cleanable.clean();
}
}
Cleaner 쓰레드를 만들 클래스는 반드시 static 클래스여야 한다. non-static 클래스(익명 클래스도 마찬가지)의 인스턴스는 그걸 감싸고 있는 클래스의 인스턴스를 잠조하지 않는다.
cleaner는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자
물론 불확실성과 성능저하에 주의해야된다.