Book/Effective Java

[Book] ITEM 8) finalizer 와 cleaner는 피하라

kkkkkkkkkkkk 2022. 3. 7. 19:33

finalizer 는 Object의 finalizer() 메서드를 말합니다.

gc 의 대상이 되는 인스턴스가 있으면 finalizer() 메서드가 작동이 되어야하는데 finalizer() 는 동작의 예측이 불가능합니다.

예제를 보면서 확인 해 봅시다.

Finalizer 클래스가 있고 오버라이드 한 finalize() 메서드와 hello() , hello2() 메서드가 있습니다.

public class Finalizer {

    @Override
    protected void finalize()throws Throwable{
       System.out.println("finalize 동작");
    }

    public void hello() {
       System.out.println("hello 동작");
    }
}

Main 클래스가 있고 hello3() 메서드 내부에 finalizer 인스턴스와 hello() 메서드가 기능을 담당하고 있습니다.

public class Main {
    public static void main(String[]args) {
        Main main = new Main();
        main.hello3();

    }
    
    private void hello3(){
        Finalizer finalizer = new Finalizer();
        finalizer.hello();
        System.out.println("hello3 동작");
    }
}

hello3() 메서드가 끝나면 FInalizer 인스턴스는 gc 에 대상이 됩니다. 그러면 finalizer() 메서드가 실행 되어야하는데 실행이 안될 것입니다.

finalizer() 메서드는 이와 같이 실행 예측이 불가능 하다라는 단점과 성능의 영향을 주어서 자바 9 에서 deprecated 가 되었고 Cleaner 라는 것이 생겨서 cleaner 를 사용해야합니다.

finalizer() 말고 Cleaner 로 사용하라는 이유는 cleaner 는 자신의 스레드를 통제하기 때문에 finalizer() 보다 좋다라고 할 수 있습니다.

하지만 Cleaner 도 finalizer() 와 마찬가지로 여전히 동작을 예측할 수 없고, 느리고 불필요합니다.

🙁  finalizer()와 Cleaner는 왜 있는 것일가요?

우리는 항상 자원을 사용 후 close() 를 해야합니다. 자원의 낭비가 되지 않게 해야하는데요.

예를 들어서 jdbc를 사용할 때 우리는 getConnection(), prepareStatement() 등 db에 접근을 하여 자원을 사용할 때 마지막에 우리는 close() 를 해야합니다.

close() 를 호출하지 않는 것을 대비하여 finalizer() 과 Cleaner 라는 것을 안정망으로 만들어 둔 것입니다.

🙁  finalizer()와 Cleaner를 대신해주는 방법은 있을가요?

AutoCloseable 인터페이스를 구현하여 close() 메서드를 호출하면 됩니다.

AutoCloseable 인터페이스를 구현하여 Cleaner 를 사용한 경우 try-with-resource 를 사용해야 합니다.

책의 예제 코드입니다.

public class Room implements AutoCloseable {

    private static final Cleaner cleaner = Cleaner.create();

    private static class Table implements Runnable {

        int cups;
        int plates;

        public Table(int cups, int plates) {
            this.cups = cups;
            this.plates = plates;
        }

        @Override
        public void run() {
            System.out.println("테이블 정리중");
            this.cups = 0;
            this.plates = 0;
        }
    }

    private final Table table;
    private final Cleaner.Cleanable cleanable;

    public Room(int cups, int plates) {
        table = new Table(cups, plates);
        cleanable =cleaner.register(this, table);
    }

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

실행 결과

정리 시작합니다.
테이블 정리중
정리 끝났습니다.
class RoomTest{

    @Test
    @DisplayName("테이블 정리 하세요")
    void RoomTest1()throws Exception{
        try(Room room = new Room(2, 5)) {
            System.out.println("정리 시작합니다.");
        }finally{
            System.out.println("정리 끝났습니다.");
        }
    }

    @Test
    @DisplayName("테이블 정리 하세요!!!!")
    void RoomTest2() {
        Room room = new Room(3, 4);
        System.out.println("정리 끝났나요?");
    }
}

실행 결과

정리 끝났나요?

Cleaner 는 안정망의 역할이니 자원의 회수용으로만 사용해야하고 성능에 문제가 있을 수 있으니 사용할 때 주의하는게 좋습니다.