peaches-book-study / effective-java

이펙티브 자바 3/E
0 stars 2 forks source link

Item 8. finalizer와 cleaner 사용을 피하라. #4

Open pyeong114 opened 7 months ago

pyeong114 commented 7 months ago

Chapter : 2. 객체 생성과 파괴

Item : 8. finalizer와 cleaner 사용을 피하라.

Assignee : eunpyeong114


🍑 서론

자바는 두 가지 객체 소멸자를 제공함

1) finalizer : 예측할 수 없고, 상황에 따라 워험할 수 있어 일반적으로 불필요함 : 오동작, 낮은 성능, 이식성 문제의 원인이 됨 => 기본적으로 쓰지 말아야 한다

2) cleaner : finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.

C++에서의 파괴자

  • 자바의 객체 소멸자와는 다른 개념!
  • C++에서의 파괴자는 생성자의 꼭 필요한 대척점으로, 특정 객체와 관련된 자원을 회수하는 보편적인 방법이다. ↔ (자바에서는 가비지 컬렉터가 접근할 수 없게 된 객체를 회수하는 역할을 담당하고 있기 때문에, 프로그래머에게는 아무런 작업도 요구하지 않음!)
  • 비메모리 자원을 회수하는 용도로도 쓰인다. ↔ (자바에서는 try-with-resources와 try-finally를 사용해 해결)

🍑 본론

finalizer와 cleaner 사용을 피하라!!

🚀 사용을 지양해야 하는 이유?

1) finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다. finalizer나 cleaner를 얼마나 신속히 수행할지는 전적으로 가비지 컬렉터 알고리즘에 달렸으며, 이는 가비지 컬렉터 구현마다 천차만별이다. 이는 객체에 접근할 수 없게 된 후 finalizer나 cleaner가 실행되기까지 얼마나 걸릴지 알 수 없다라는 의미이다. 그렇기에 finalizer와 cleaner는 즉시 수행된다는 보장이 없다.

2) 수행 여부 또한 보장하지 않는다. 종료 작업을 전혀 수행하지 못한 채 중단될 수도 있다. 따라서 프로그램 생애주기와 상관없는 상태를 영구적으로 수정하는 작업에서는 사용해서는 안된다.

3) 예외 처리 finalizer 동작 중 발생한 예외는 무시되며, 처리할 작업이 남았더라도 그 순간 종료된다. 잡지 못한 예외로 인해 해당 객체는 마무리가 덜 된 상태로 남을 수 있다.

4) 성능 문제 가비지 컬렉터의 효율을 떨어뜨리고 안전망의 설치의 대가로 약 5배 정도 느려진다.

5) 보안 문제 finalizer는 생성자나 직렬화 과정에서 예외가 발생한다면 이 생성되다만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있게 된다. 또한 이 finalizer는 정적 필드에 자신의 참조를 할당해 가비지 컬렉터가 수집하지 못하게 막을 수 있다. finalizer를 final로 선언해 해결할 수 있다.

🚀 파일이나 스레드 등 종료해야 할 자원을 담고 있는 객체의 클래스에서 finalizer나 cleaner를 대신할 묘안은?

AutoCloseable을 구현해주고, 클라이언트에서 인스턴스를 다 쓰고 나면 close 메서드를 호출해주는 방식이 있다. 여기서 close 메서드에서는 이 객체는 더 이상 유효하지 않음을 필드에 기록하고, 다른 메서드는 이 필드를 검사해 객체가 닫힌 후 불렸다면 IllegalStateException을 던지면 된다.

🚀 Cleaner, Finalizer가 (아마도) 적절한 경우

1) 자원의 소유자가 close 메서드를 호출하지 않는 것에 대한 대비망 즉시 호출된다고 보장은 없지만 자원 회수를 늦게라도 해주므로 안전망 역할이다. 예를 들어, FileInputStream, FileOutputStream, ThreadPoolExecutor가 존재한다.

2) 네이티브 피어와 연결된 객체에서의 사용 native peer란 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 말한다. 네이티브 피어는 자바 객체가 아니여서 가비지 컬렉터는 그 존재를 알지 못한다. 따라서 자바 피어를 회수할 때 네이티브 피어도 회수하지 못해서 cleaner나 finalizer가 나서서 처리할 수 있다.

다음은 cleaner를 이용해 안전망으로 사용하는 예제이다.

public class Room implements AutoCloseable{
    private static final Cleaner cleaner = Cleaner.create();

    //Room을 참조하면 순환으로 참조하기에 가비지 컬렉터의 대상이 되지 않으므로 Room을 참조해서는 안된다.
    //청소가 필요한 자원, cleaner가 청소할 때 수거할 자원을 가진다.
    private static class State implements Runnable{
        int numJunkPiles; // 수거대

        public State(final int numJunkPiles) {
            this.numJunkPiles = numJunkPiles;
        }
        //close나 cleaner메소드가 호출한다.
        @Override
        public void run() {
            System.out.println("방청소");
            numJunkPiles = 0;
        }
    }

    private final State state; //방 상태

    private final Cleaner.Cleanable cleanable; //수거 대상이 된다면 방을 청소한다.

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

    @Override
    public void close() throws Exception {
        cleanable.clean();
    }
}

🍑 결론

🚀


Referenced by

-