본문 바로가기
JAVA/[JAVA] 바구니

finalizer와 cleaner 사용을 피하라

by oncerun 2022. 11. 7.
반응형
  • finalizer와 cleaner는 즉시 수행된다는 보장이 없다.
  • finalizer와 cleaner는 실행되지 않을 수도 있다.
  • finalizer 동작 중에 예외가 발생하면 정리 작업이 처리되지 않을 수도 있다.
  • finalizer와 cleaner는 심각한 성능 문제가 있다.
  • finalizer는 보안 문제가 있다.
  • 반납할 자원이 있는 클래스는 AutoCloseable을 구현하고 클라이언트에서 close()를 호출하거나 try-with-resource를 사용해야 한다.

 

객체가 사용하는 자원을 반납하지 않는다면 반드시 오류가 발생한다. 이래서 리소스를 반납하기 위해 제공되는 것이 finalizer, cleaner를 제공한다.  

 

finalizer

 

finalizer는 Object에서 구현하고 있는 메서드이다.

@Override
protected void finalize() throws Throwable {
    super.finalize();
}
@Deprecated(since="9")
protected void finalize() throws Throwable { }

 

자바 9 버전부터 사용을 자제하라고 권장하고 있다. 

관련 내용에서는 Cleaner, PhantomReference,  AutoCloseable를 사용하라고 link가 걸려있다.

 

finalizer는 내부적으로 RefrenceQueue를 가지고 있다. 이 큐 안에 가비지 컬렉터의 대상이 들어가는데 실제 동작을 확인하면 queue의 정리가 잘되지 않는다.

이는 큐를 처리하는 스레드의 우선순위가 더 낮기 때문이다.

 

Finalizer Attack

 

만들다 만 객체를 finalize 메서드에서 사용하는 방법.

생성자가 막혀있는 객체를 상속받아 finalize를 오버라이드 하는 것.

 

상속받은 객체를 생성할 때 부모 생성자를 호출할 때 발생하는 예외를 잡고 gc를 발생시킨다.

gc 발생함에 따라 상속받은 객체의 finalize가 발생하면서 코드가 실행된다.

 

막는 방법은 간단하다. 부모 클래스에서 finalize를 final 키워드로 막아버리는 것이다.

 

 

 

cleaner

 

public class BigObject {

    private List<Object> resource;

    public BigObject(List<Object> resource) {
        this.resource = resource;
    }

    public static class ResourceCleaner implements Runnable {

        private List<Object> resourceToClean;

        public ResourceCleaner(List<Object> resourceToClean) {
            this.resourceToClean = resourceToClean;
        }

        @Override
        public void run() {
            resourceToClean = null;
        }
    }


}

 

특이하게 Runnable task를 사용하여 리소스를 정리한다. 그리고 절대로 정리하려는 오브젝트를 참조하면 안 된다. 

 

만약 내부 클래스로 Runnable을 정의하려는 경우 반드시 정적 내부 클래스로 만들어주어야 한다.

그렇지 않을 경우 내부 클래스에는 자동적으로 외부 클래스를 참조하고 있기 때문이다.

 

 

 

Cleaner는 PhantomReference를 사용해서 만들어졌기 때문에 사용 방법이 매우 비슷하다.

 

 

cleaner를 생성하고 제거의 대상이 되는 객체를 넣고 해당 옆에 Runnable을 전달한다.

 

 

이 중 가장 적절한 해결책은  AutoCloseable이다.

 

public class AutoClosable implements AutoCloseable {
    @Override
    public void close() throws Exception {
        
    }
}

AutoCloseable인터페이스는 close 한가지 메서드만을 가지고 있다. 

 

만약 IO관련 작업을 한다면 Closeable 인터페이스를 사용하는 것도 추천한다.

 

 

 

사용 시 우리는 이 close를 재정의하면 된다. 

public class AutoClosable implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("실행");
    }

    public static void main(String[] args) {
        try (AutoClosable autoClosable = new AutoClosable()) {

        } catch (Exception e) {
            
        }
    }
}

 

그리고 이 AutoCloseable를 구현한 클래스를 사용하는 쪽에서는 try-with-resource를 사용한다면 자원 정리를 할 수 있다. 

 

try-with-resource

 

try-finally는 더 이상 최선의 방법이 아니다 (java 7) 

 

사용하는 자원이 여러 개 인경우 수많은 try-finally 중첩이 발생한다. 이는 가독성을 많이 망가뜨린다.

 

대부분의 자원을 사용한는 객체는 Closeable, AutoCloseable을 impl 하고 있는 것을 확인할 수 있을 것이다.

 

더 좋은 것은 자원의 사용에 따른 닫는 순서를 지켜 코드를 작성하는 것이 아닌 각각 close()를 시도해준다는 것이다.  또한 추가적으로 각 각의 예외를 전부 제공해준다. 

 

 

Cleaner를 사용해 gc가 일어날 때 cleaner queue에 들어가 gc를 통해 자원을 삭제할 것 인지 선택할 수 있다.

 

이는 클라이언트가 try-with-resoruce를 통해 정리하지 않는 경우를 대비해 대비책을 마련한 것이다.

 

또 AutoCloseable을 사용해 try-with-resource를 통해 정리를 할 건지 선택할 수 있다.

 

 

 

뭔가 큰 오브젝트를 정리가 필요할 때 관련 내용에 대해 깊게 공부하는 것도 나쁘지 않아 보인다.

반응형

'JAVA > [JAVA] 바구니' 카테고리의 다른 글

Comparable  (0) 2022.11.14
equals 정의  (0) 2022.11.10
Exception  (0) 2022.09.25
SSL/TLS 서버 통신 (JSSE, TrustManager)  (1) 2021.11.06
짧)[JAVA] 객체지향 세계  (0) 2021.04.25

댓글