작성자: 김대희
작성일: 2022년 4월 16일
- 예측할 수 없고 상황에 따라 위험할 수 있어 일반적으로 불필요하다.
- 오동작, 낮은 성능, 이식성 문제의 원인이 되기도 한다.
- 따라서 기본적으로 쓰지 말아야한다.
- finalizer의 대안으로 소개되지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.
C++ 프로그래머라면 주의해야할 내용
finalizer와 cleaner는 C++의 destructor와 다른 개념이며, destructor는 constructor의 대척점이다.
하지만, 자바는 접근할 수 없는 객체를 회수하는 역할은 가비지 컬렉터가 담당한다.
- 즉시 수행된다는 보장이 없다. 즉, finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다. → 파일 닫기 같은 작업을 맡기면 중대한 오류를 일으킬 수 있다.
- 수행 시점 뿐만이 아니라 수행 여부 조차 보장하지 않는다. → 꼭 반납해야하는 자원을 반납하지 못한 채 프로그램이 중단될 수도 있다.
- finalizer 동작 중 발생한 예외는 무시되며, 처리할 작업이 남았더라도 그 순간 종료된다.
- 또한, 심각한 성능 문제 또한 동반한다. try-with-resources 와 비교하여 약 50배나 느릴 수 있다.
- finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수도 있다.
- final이 아닌 클래스를 finalizer 공격으로부터 방어하려면 아무 일도 하지 않는 finalize 메서드를 만들고 final로 선언하자.
- AutoCloseable 을 구현해주고. 클라이언트에서 인스턴스를 다 쓰고 난다면 close 메서드를 호출한다. (예외가 발생해도 제대로 종료되도록 try-with-resources를 사용해야 한다)
- 각 인스턴스는 자신이 닫혔는지를 추적하는 것이 좋다. → close 메서드에서 이 객체가 유효하지 않음을 필드에 기록하고, 다른 메서드는 이 필드를 검사해서 닫힌 후에 불렸다면 IllegalStateException을 던지도록 함.
-
자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할로, 클라이언트가 하지 않은 자원 회수를 늦게라도 해주는 것이 낫다는 의미.
-
네이티브 피어(native peer)와 연결된 객체
네이티브 객체란?
- 네이티브 피어란 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체
- Java 외의 프로그래밍 언어(C/C++ 이나 어셈블리)로 컴파일한 프로그램을 지칭하며, 이를 라이브러리로써 자바 피어가 실행할 수 있게 해주는 인터페이스를 JNI (Java Native Interface)라고 한다.
- 자바 객체가 아니니 가비지 컬렉터는 그 존재를 알지 못한다. 그 결과 가비지 컬렉터가 네이티브 객체는 회수하지 못한다.
- 이런 경우 cleaner나 finalizer를 사용하며, 성능 저하를 감당할 수 있고, 심각한 자원을 가지고 있지 않을 경우 해당된다. → 즉시 회수해야 한다면 close 메서드를 사용해야한다.
class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// 청소가 필요한 자원으로 Room을 참조하게되면 순한 참조가 일어나기에 회수하지 못한다.
// 따라서 절대 Room을 참조하지 말 것!
private static class State implements Runnable {
int numJunkPile;
public State(int numJunkPile) {
this.numJunkPile = numJunkPile;
}
@Override
public void run() {
System.out.println("방 청소");
numJunkPile = 0;
}
}
private final State state;
private final Cleaner.Cleanable cleanable;
public Room(int numJunkPile) {
this.state = new State(numJunkPile);
cleanable = cleaner.register(this, state);
}
@Override
public void close() throws Exception {
cleanable.clean();
}
}- State 인스턴스는 절대로 Room 인스턴스를 참조해서는 안된다 → 순환 참조 발생으로 가비지 컬렉터가 회수 할 수 없게 됨.
- 이는 State가 정적 중첩 클래스인 이유로, 정적이 아닌 클래스는 자동으로 바깥 객체의 참조를 갖게된다.
- 비슷하게 람다 역시 바깥 객체의 참조를 갖기 쉬우니 사용하지 않는 것이 좋다.
cleaner는 Java 9 에서 finalizer의 대안으로 도입되었으며, cleaner객체에 모니터링할 object와, 객체가 소멸할 때 자원을 회수하는 action을 등록하고, clean() 메서드가 수행되면 action을 실행하는 방식으로 동작한다.
