-
Item 8. finalizer와 cleaner 사용을 피하라Book/Effective Java 3E 2022. 10. 13. 08:00반응형
자바는 두 가지 객체 소멸자를 제공합니다. 다만, 두 가지 객체 소멸자 모두 예측할 수 없고, 일반적으로 불필요합니다.
- finalizer: 자바 9 deprecated API 지정. cleaner를 대안으로 소개.
- cleaner: 자바 9부터 지원하는 객체 소멸자.
finalizer와 cleaner는 즉시 수행된다는 보장이 없습니다.[JLS, 12.6] 즉, finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없습니다. 자바 언어 명세는 finalizer나 cleaner의 수행 시점뿐 아니라 수행 여부조차 보장하지 않습니다. 따라서 프로그램 생애주기와 상관없는, 상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존해서는 안 됩니다. 또한, finalizer와 cleaner는 심각한 성능 문제도 동반하며, finalizer를 사용한 클래스는 finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수도 있습니다.
파일이나 스레드 등 종료해야 할 자원을 담고 있는 객체의 클래스에서 finalizer나 cleaner를 대신해줄 묘안은 클라이언트에서 인스턴스를 다 쓰고 나면 close 메서드를 호출하면 됩니다(일반적으로 예외가 발생해도 제대로 종료되도록 try-with-resources를 사용해야 합니다. Item 9).
cleaner와 finalizer의 적절한 쓰임새
- 자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할
cleaner나 finalizer가 즉시 호출되리라는 보장은 없지만, 클라이언트가 하지 않은 자원 회수를 늦게라도 해주는 것이 아예 안 하는 것보다는 나은 경우 사용합니다. 자바 라이브러리의 일부 클래스는 안전망 역할의 finalizer를 제공합니다. FileInputStream, FileOutputStream, ThreadPoolExecutor가 대표적입니다. - 네이티브 피어(native peer)와 연결된 객체
네이티브 피어란 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 말합니다. 네이티브 피어는 자바 객체가 아니기에 자바 피어를 회수할 때 네이티브 객체까지 회수하지 못합니다. cleaner나 finalizer가 처리하기 적합한 작업입니다.
다음은 cleaner를 사용한 Room 클래스입니다. 방(room) 자원을 수거하기 전에 반드시 청소(clean)해야 한다고 가정합니다.
package effectivejava.chapter2.item8; import java.lang.ref.Cleaner; // 코드 8-1 cleaner를 안전망으로 활용하는 AutoCloseable 클래스 public class Room implements AutoCloseable { private static final Cleaner cleaner = Cleaner.create(); // 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다! // 순환참조가 생겨 가비기 컬렉터가 Room 인스턴스를 회수해갈 기회가 오지 않는다. private static class State implements Runnable { int numJunkPiles; // Number of junk piles in this room State(int numJunkPiles) { this.numJunkPiles = numJunkPiles; } // close 메서드나 cleaner가 호출한다. // 1. Room의 close 메서드 호출 > Cleanable의 clean 호출 > run 호출 // 2. 가비지 컬렉터가 Room 회수 > cleaner(바라건대) State의 run 호출 @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(); } }
Room의 cleaner는 단지 안전망으로만 쓰였습니다. 클라이언트가 모든 Room 생성을 try-with-resources 블록으로 감쌌다면 자동 청소는 전혀 필요하지 않습니다. 다음은 잘 짜인 클라이언트 코드의 예입니다.
package effectivejava.chapter2.item8; // cleaner 안전망을 갖춘 자원을 제대로 활용하는 클라이언트 public class Adult { public static void main(String[] args) { try (Room myRoom = new Room(7)) { System.out.println("안녕~"); } } }
기대한 대로 Adult 프로그램은 "안녕~"을 출력한 후, 이어서 "Cleaning room"를 출력합니다.
다음은 결코 방 청소를 하지 않는 프로그램입니다.
package effectivejava.chapter2.item8; import java.util.concurrent.TimeUnit; // cleaner 안전망을 갖춘 자원을 제대로 활용하지 못하는 클라이언트 public class Teenager { public static void main(String[] args) { new Room(99); System.out.println("Peace out"); // 다음 줄의 주석을 해제한 후 동작을 다시 확인해보자. // 단, 가비지 컬렉러를 강제로 호출하는 이런 방식에 의존해서는 절대 안 된다! // System.gc(); } }
cleaner의 명세에는 "System.exit을 호출할 때의 clenaer 동작은 구현하기 나름이다. 청소가 이뤄질지는 보장하지 않는다."라고 쓰여 있습니다.
cleaner(자바 8까지는 finalizer)는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자.
물론 이런 경우라도 불확실성과 성능 저하에 주의해야 한다.
[참고 정보]
이펙티브 자바 Effective Java 3/E 도서 [조슈아 블로크 저]
반응형'Book > Effective Java 3E' 카테고리의 다른 글
Item 10. equals는 일반 규약을 지켜 재정의하라 (0) 2022.10.17 Item 9. try-finally보다는 try-with-resources를 사용하라 (0) 2022.10.13 Item 7. 다 쓴 객체 참조를 해제하라 (0) 2022.10.12 Item 6. 불필요한 객체 생성을 피하라 (0) 2022.10.10 Item 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) 2022.10.10