# ITEM8 finalizer 와 cleaner 사용을 피하라

이번 내용은 잘 와닿지 않아서 내용을 적기만 하였습니다. 얼마나 의미가 있을까 모르겠네요 try-with-resources를 위한 내용인 것 같기도 합니다.

# 문제점들

자바의 두가지 객체 소멸자 finalizer cleaner

  • 예측이 어려움
  • 일반적으로 불필요함
  • 오동작, 낮은성능, 이식성 문제, 위험성

# 즉시 실행된다는 보장이 없다.

  • 객체에 접근할 수 없게 된 후 finalizer / cleaner 의 수행시점을 알 수 없다.
  • finalizer / cleaner 의 수행은, 전적으로 GC 알고리즘에 달렸다.
  • finalizer 스레드는 다른 애플리케이션 스레드보다 우선순위가 낮아 실행 기회를 제대로 얻지 못할 수 있다.
  • cleaner 는 자신을 수행할 스레드를 제어할 수 있다.

# 수행 여부를 보장하지 않는다.

데이터베이스 같은 공유 자원의 영구 Lock 해제를 finalizer 나 cleaner 에 맡겨 놓으면 분산 시스템 전체가 서서히 멈출것 ?

  • 상태를 영구적으로 수정하는 작업에서는 절대 의존해선 안된다.

# finalizer 동작 중 발생한 예외는 무시된다.

경고 조차 출력하지 않음.

잡지 못한 예외 때문에 해당 객체는 자칫 마무리가 덜 된 상태로 남을 수 있다.

  • cleaner 를 사용하는 라이브러리는 자신의 스레드를 통제하기 때문에 이런 문제가 없다.

# 심각한 성능 문제

GC 의 효율을 떨어 뜨린다.

# finalizer 공격에 노출되어 심각한 보안 문제

finalizer 공격 원리

  • 생성자나 직렬화 과정에서 예외가 발생하면,
    생성 되다만 객체에서 악의적인 하위 클래스의 finalizer 가 수행될 수 있다.

# 악의적인 하위 클래스의 finalizer

  • finalizer 는 정적 필드에 자신의 참조를 할당하여 GC 가 수집하지 못하게함

객체 생성을 막으려면 생성자에서 예외를 던지는 것만으로 충분하지만, finalizer 가 있다면 그렇지 않다.

  • final 클래스는 하위 클래스를 만들수 없으니 finalizer 공격에서 안전함.
  • final 클래스가 아니면, 아무일도 하지 않는 finalize 메서드를 만들고 final 로 선언하자.

?? 실제 finalizer 공격이 발생한 사례가 무엇이 있을까?

# finalizer / cleaner 의 대안

# AutoClosable 의 구현

# 클라이언트에서 인스턴스를 다 쓰고나면 close 메서드 호출

예외가 발생해도 제대로 종료되도록 try-with-resources 를 사용 [9]

# 인스턴스 자신이 닫혔는지 추적하기

  1. close 메서드 에서, 객체가 유효하지 않음을 필드에 기록한다.
  2. 다른 메서드는 이 필드를 검사해서 객체가 닫힌 후 불렸다면 IllegalStateException 을 던진다.

?? 2 번의 방법은 어떻게 쓰이는 것일까? 얼마나 쓰일까?

# finalizer / cleaner 의 쓰임새

# 클라이언트가 하지 않은 자원회수를 늦게라도 해주는게 낫다.

FileInputStrean, FileOutputStream, ThreadPoolExecutor 가 대표적으로 안전망 역할의 finalizer 를 제공함.

# 네이티브 피어와 연결된 객체

# Native Peer

  • 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체
  • 네이티브 피어는 자바 객체가 아니니 GC 는 존재를 알지 못하여 회수하지 않음

# 조건

  • 성능저하를 감당할 수 있어야 한다.
  • 네이티브피어가 심각한 자원??을 가지고 있지 않아야 한다.

# close 메서드

  • 성능저하를 감당할 수 없음
  • 네이티브 피어가 사용하는 자원을 즉시 회수해야 할 경우

close 메서드를 사용해야 한다.

# cleaner

cleaner 는 사용하기에 조금 까다롭다.

Room 클래스로 이 기능을 설명하고 있다.

  • room 을 수거하기 전, 반드시 clean 청소 해야 한다.
  • Room 클래스는 AutoCloseable 을 구현한다.
  • 자동 청소 안전망이 cleaner 를 사용하지 말지는 내부 구현 방식에 관한 문제다.
    • finalizer 와 달리 cleaner 클래스의 public API 에 나타나지 않는다. ??

cleaner 를 안전망으로 활용하는 AutoCloseable 클래스

public class Room implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();
        
    // 청소가 필요한 자원. 절대 Room 을 참조해선 안된다.
    // Room 인스턴스를 호출할 경우 순환참조가 생겨 GC 가 Room 인스턴스를 회수하지 않음
    // 정적이 아닌 중첩 클래스는 자동으로 바깥 객체의 참조를 갖는다.
    // 람다도 바깥객체의 참조를 갖기 쉽다.
    private static class State implements Runnable {
        int numJunkPiles;   // Room 안의 쓰레기 수
        
        State(int numJunkPiles) {
            this.numJunkPiles = numJunkPiles;
        }
    
        // close 메서드나, cleaner 가 호출된다. .. 
        // cleanable 에 의해 딱 한번 호출된다고 함.
        // 보통은 Room 의 close 를 호출할 때 호출됨
        // GC 가 Room 을 회수할 때까지 클라이언트가 close 를 호출하지 않는다면 
        // cleaner 가 State 의 run 메서드를 호출 할 것임.
        @Override public void run() {
            System.out.println("방 청소");
            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();
    }
} 
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

클라이언트가 모든 Room 생성을 try-with-resources 블록으로 감쌌다면 자동 청소는 전혀 필요하지 않다. System.out.println("방 청소"); 가 같이 실행 될 것이다.

public class Adult {
    public static void main(String[] args) {
        try (Room myRoom = new Room(7)) {
            System.out.println("hi~");
        }
    }
}
1
2
3
4
5
6
7

만약 try-with-resources 블록 이 없다면, System.out.println("방 청소"); 가 출력되지 않을 수 있다. clean 의 예측할 수 없는 특징 때문이다.