Item8. finalizer와 cleaner 사용을 피해라

Java는 두가지 객체 소멸자를 제공한다.

  1. finalizer

  2. cleaner

여기서 객체 소멸자란 생성자와 정반대의 개념으로, 객체가 소멸될떄(GC될때)
자동으로 호출되는 함수이다.

Java 9에서부터는 finalizer를 사용 자제해야 할 API로 지정하고, cleaner를 그 대안으로 소개하였다. 하지만 책에서는 cleaner또는 finalizer의 사용을 피하라고 얘기하고 있다,.그렇다면 finalizer와 cleaner는 왜 사용하면 안될까?

1
2
3
4
5
@Override // deprecated 
protected void finalize() throws Throwable {
super.finalize();
}

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

객체의 소멸을 GC가 담당하므로, finalizer와 cleaner를 언제 수행할지의 역할은 전적으로 GC 알고리즘에 달려있다. 즉 GC 구현마다 차이가 있다.

위와 같은 특징들을 보면 당연히 finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다.

단점 2. finalizer는 instance 반납을 지연시킬 수도 있다.

finalizer 쓰레드는 우선순위가 낮아서 , 언제 실행될지 모른다. -> 계속 대기 된다면 GC되지 않고, OutOfMemoryException이 발생할수도 있다.

단점 3. finalize 동작 중에 발생한 에러는 무시되며, 처리할 작업이 남았더라도, 그 순간 종료된다.

중간에 예외가 터지면, 객체가 훼손된 상태로 남아있을수도 있다.

단점 4. 성능 문제

AutoCloseable 객체를 만들고, try-with-resoucre 로 자원 반납을 하는데 걸리는 시간은 12ns 인데 비해, Finalizer는 550ns 거의 약 50배가 걸렸다. cleaner도 60ns로, 5배정도 느리다.

그렇다면 finalizer나 cleaner를 어떻게 대체해주어야 하는가?

  • 반환할 자원 class에 AutoCloseable 을 구현해주고, client에서 instance를 다 쓰고 나면 close method을 호출해주면 된다.

finalizer와 cleaner의 적절한 용도

  1. 자원 소유자가 close method를 깜빡하고 호출하지 못하였을때, 안전망 역할을 해준다.

  2. 자바 객체가 아닌 native peer 회수

cleaner 사용예시 - 리소스 반환을 위한 안전망 역할

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41


public class Room implements AutoCloseable{


private static final Cleaner cleaner = Cleaner.create();



private static class State implements Runnable{
int numJunkPiles; //

public State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}

@Override
public void run() {
System.out.println("clean room");
numJunkPiles = 0;
}
}

private final State state;

private final Cleaner.Cleanable cleanable;

public Room(int junk) {
state = new State(junk);
cleanable = cleaner.register(this,state);
}


@Override
public void close() throws Exception {
// 자원 사용하는 client가 리소스를 깜빡하고 반환하지 못하였을때, cleaner가 안전망 역할로써 이를 반환해준다 (gc될떄 호출됨 )
cleanable.clean();
}
}


위 예시처럼 Room client가 명시적으로 close를 호출하는것을 깜빡하였다면 room이 GC에 의해 회수될때 cleaner가 state의 run method를 호출해줌으로서 , 리소스를 회수하는 역할을 해준다. (안전망 역할)

하지만 이조차도 try-with-resouces 블록으로 감싸면 필요하지 않다.

예를 들면 다음과 같다.

1
2
3
4
5
6
7

public static void main(String[] args) throws Exception {
try(Room myRoom = new Room(8)){
new Room(99);
System.out.println(" try - with -resource " );
}
}

정리하면 java 8 : finalizer / java 9 이후 : cleaner는 안전망 역할이나 네이티브 피어 객체 회수용 정도로만 사용하고, 이후에는 사용하지 말고, 대신 try-with-resource로 안전하게 자원을 반환하자는 내용이다.

Comments