Java

[Effective Java] 아이템8. finalizer와 cleaner 사용을 피하라 (finalizer/cleaner의 단점, 대안 AutoClosable)

jun9.com 2022. 3. 20. 04:43

1. finalizer와 cleaner

- 자바에서 제공하는 개체 소멸자이다. 

 

finalizer

- 참조하지 않는 배열이나 객체를 Garbage Collector를 사용해 힙 영역에서 제거시킨다.

- Object 클래스에서 제공하는 기본 메서드이기 때문에 어느 클래스에서든지 finalize 메서드를 오버라이드 할 수 있다.

- 오동작, 낮은 성능, 이식성 등의 문제로 자바9에서 deprecated되었다. 웬만하면 사용하지 않아야한다. 

cleaner란?

- finilizer 대안으로 등장했지만 이 역시 느리고, 보통 불필요하다.

 

 

2. finalizer와 cleaner의 단점

1)즉시 실행됨이 보장되지 않는다.

때문에 제때 실행되어야 하는 작업은 수행할 수 없다. 

예를 들어  파일 닫기를 filnalizer/cleaner에 맡긴다면,

파일이 닫히지 않는 경우가 생길 수 있다.

또한 파일을 계속 열어둔다면 새로운 파일을 열지 못해 프로그램이 실패할 수 있다. 

 

더군다나 filnalize는 다른 어플리케이션 스레드보다 우선순위가 낮아 자원회수가 지연된다.

또한 동작도중에 발생한 예외는 무시되고, 남은 작업은 수행하지 않고 종료된다.

-> 작업이 완료되지 않고 객체가 훼손된다. 다른 스레드가 이 객체를 사용하려하면 원래 의도와 다른 동작을 할 수 있다.

 

cleaner는 자신이 수행할 스레드를 제어할 수 있다는 측면에서는 finalizer보다 낫지만

이 역시 gc 통제하에 있어 즉각 수행될거라는 보장은 없다. 

 

 

2) 수행 여부가 보장되지 않는다.

즉각 수행을 보장해주는 메서드도 등장했었지만(System.runFilizerOnExit, Runtime.runFinalizersOnExit)

심각한 결함때문에 잘 사용하지 않는다.

 

3) 느리다

finalizer와 cleaner는 가비지 컬텍터의 효율을 떨어뜨린다.

AutoClosable객체를 생성하는 것(현재까지 가장 좋은 자원 반납 방법)보다 50배나 느리다.

 

4) 보안문제

생성자나 직렬화 과정에서 예외가 발생하면,

생성되다 만 객체에서 악의적인 하위 클래스의 finalize가 수행될 수 있다.

 

solution) final class로 생성하거나 아무일도 하지 않는 finalize메서드를 만들고 final로 선언한다.

-> final클래스는 하위 클래스를 만들 수 없기 때문에!!

 

3. 대안) AutoClosable

- 정상적으로 자원을 반납하는 방법.

- AutoClosable을 구현하고, 클라이언트에서 인스턴스를 다 쓰고 close메서드를 호출한다.

- 예외가 발생해도 제대로 종료되도록 try-with-resources(아이템9)를 사용한다.

- 각 인스턴스가 자신이 닫혔는지 추적할 수 있도록 필드에 기록한다. 다른 메서드는 이 필드를 검사해 객체가 닫힌후에 close가 불렸다면 예외를 던진다.

 

 

4. 그렇다면  finalizer와 cleaner는 아예 쓸모가 없을까?

1) close메서드가 호출되지 않았을 때 늦게라도 자원을 반납하기 위해 사용할 수 있다.

AutoCloseable을 구현하지 않았을 경우를 대비한 안전망 역할로 사용할 수 있다.

ex: FileInputStream, FileOutputStream, ThreadPoolExecutor

 

[Java8 - FileInputStream.class] 

public
class FileInputStream extends InputStream // InputStream은 Closable을 implements
{
...

public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }
        if (channel != null) {
           channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }
    
  ...  

protected void finalize() throws IOException { 
    if ((fd != null) &&  (fd != FileDescriptor.in)) {
        close();
    }
}

...
}

 

2) 네이티브 피어(Native Peer)와 연결된 객체에서 사용할 수 있다.

- 네이티브 피어(Native Peer)란?  자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체

- 네이티브 메소드(Native Method)란? 다른 언어로 작성된 코드를 자바에서 호출하도록 만들어진 규약

 

즉 자바 객체가 아니기 때문에, 가비지 컬렉터는 이 객체의 존재를 알지 못한다.

이럴 때 finalizer나 cleaner로 회수한다. 

다만 성능저하가 발생하므로 이를 감당할 수 있어야 하고,

네이티브 피어가 심각한 자원을 가지고 있지 않을 때 해당한다.