c++에서는 객체 소멸자가 기본으로 제공되지만. 자바의 경우 finalizer와 cleaner를 활용해 구현해야한다. finalizer의 경우 java 9 이후로 deprecate 되었고, 그 이후 대안으로 cleaner를 사용한다.
메모리와 외부 시스템 연결과 같은 작업은 사용후 객체 파괴시 연결을 끊어줘야하는데 문제는 finalizer와 cleaner의 경우 호출 시점이 명확하지 않다는 점이다. 해당 호출은 전적으로 GC에게 맡긴다. 문제는 cleaner 호출 시점이 명확하지 않으면 OutOfMemoryError와 같이 메모리가 터지는 불상사가 발생할 수 있다.
책에서는 try - with - resource 를 적극 권장한다. 그 이유는 코드가 간결하고 자원을 다 쓴 시점에서 명시적으로 해제가 가능하기 때문이다.
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("방청소");
numJunkPiles = 0;
}
}
private final State state;
private final Cleaner.Cleanable cleanable;
public Room(int numJunkPiles) {
this.state = new State(numJunkPiles);
this.cleanable = cleaner.register(this, state);
}
@Override
public void close() throws Exception {
cleanable.clean();
}
}
Room이라는 클래스의 AutoCloseable과 cleaner를 활용
class SolutionTest {
@Test
void test() {
Room room = new Room(7);
room = null;
System.out.println("아무렴");
System.gc();
}
}
gc를 이용해 가비지 컬렉터를 호출하면 Room(7) 에 연결된 자원은 없으므로 GC가 호출하고 자원을 제거한다. 이때 Cleaner가 호출되는 모습을 볼 수 있다.
하지만 System.gc()의 경우 컴퓨터나 다른 기기마다 매번 잘 동작한다는 것을 보장할 수 없다. 그리고 Intellij에서도 try - with -resource 방법을 추천한다.
바람직한 방법은 다음과 같다.
class SolutionTest {
@Test
void test() throws Exception {
try (Room room = new Room(7)) {
System.out.println("아무렴");
}
}
}
이 방법은 Room(7) 수명이 try 블록 내부 동작까지 동작한다는 것을 보장한다. 명시적으로 room이라는 자원을 회수해가기 때문에 외부 시스템 자원 연동과 같이 객체 삭제시 별도의 작업이 필요한 경우 try-with-resource를 활용하자.
왠만하면 구현할 일이 없고 피하는 것이 좋다. 라이브러리의 경우 혹시나 해제 못했을 경우 GC 호출시 호출하도록 최후의 보루 형태로 쓴다고 한다. 예를 들면 FileInputStream과 같은 경우인데 혹시나 명시적으로 해제를 못하는 경우, GC로 해당 객체 호출시 finalizer 작업을 수행한다. 그러나 이런 실수는 하면 안된다...