[Effective Java]Item8. finalizer와 cleaner 사용을 피하라

최강일·2024년 4월 6일
0

Effective Java

목록 보기
7/9

java는 2가지 객체 소멸자를 제공한다.
그중 finalizer는 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다.
오동작, 낮은 성능, 이식성 문제의 원인이 되기도 한다.
finalizer는 나름의 쓰임새가 몇 가지 있긴 하지만 기본적으로 쓰지 말아야 한다.
그래서 java9에서는 finalizer를 사용 자제 api로 지정하고 cleaner를 그 대안으로 소개했다.
cleaner는 finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.

즉시 수행된다는 보장이 없다.

finalizer와 cleaner는 즉시 수행된다는 보장이 없다.
객체에 접근할 수 없게 된 후 finalizer나 cleaner가 실행되기까지 얼마나 걸릴지 알 수 없다.
즉, finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다.

예를들어, 파일닫기 기능을 이 2가지에게 맡긴다고 가정해보자.
언제 실행될지 모르기에 파일이 닫히지 않고 있는데, 새로운 파일이 계속 열리게되면 어떨까?
결국 이계치를 넘어 프로그램 자체가 동작하지 않게된다.

그러면 finalizer와 cleaner를 빨리 수행될 수 있게 하면 되는거 아닐까?
아니다. 이는 전적으로 가비지컬렉터 알고리즘에 달려있다.
즉, jvm 환경마다 다르게 돌아간다는 말이다.
테스트할 때는 괜찮았는데 리얼에서는 안될 수 있다는 것이다.

finalizer 스레드 자체가 다른 어플리케이션 스레드보다 우선순위가 낮기 때문에,실행 대기열에서 계속 순서가 밀리면서 자원 회수가 지연될 수 있다.
cleaner의 경우 자기 자신을 수행할 스레드를 제어할 수 있지만, 여전히 가비지컬렉터 통제하에 있기 대문에 즉각 수행된다는 보장이 없다.

수행 시점뿐 아니라 수행 여부조차 보장하지 않는다.

쉽게 말해, 프로그램이 끝났을때 과연 원하고자 했던 자원 회수가 됬는지 안됬는지조차 알 수가 없다는 말이다.

그렇기 때문에, 상태를 영구적으로 수정하는 작업에서는 (프로그램이 죽건 말건 상관이 없는), 절대 finalizer와 cleaner에 의존해서는 안된다.

System.gc나 System.runFinalization 메서드에 현혹되지 말라.

finalizer와 clieaner가 실행될 가능성을 높여줄 수는 있으나 보장하진 않는다.

finalizer에서 발생한 예외는 무시되며, 처리할 작업이 남아있어도 종료된다.

결국 예외 하나 때문에 객체 자체가 마무리가 덜 된 상태로 프로그램이 끝날 수도 있다는 말이다.
그렇게 훼손된 상태로 끝난 객체를 또 다른 자원에서 사용하려고 한다면, 더 큰 문제를 야기할수도 있을것이다.
또 원래는 예외가 발생하면 스레드가 중단되고 스택 추적 내용이 나오지만, finalizer는 경고조차 나오지 않는다.

finalizer와 cleaner는 심각한 성능 문제도 동반한다.

finalizer는 사용 그 자체로 가비지컬렉터의 효율을 떨어뜨린다.
cleaner도 클래스의 모든 인스턴스를 수거하는 형태로 사용할 경우 finalizer와 비슷하다.

자원 회수에 finalizer를 사용할 경우, try-with-resources를 사용했을 때보다 50배가 느려지가 된다.

finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수도 있다.

finalizer 공격이란, 객체가 생성되는 과정에서 예외가 발생했을 때, 이 생성되다 만 객체의 하위 클래스에서 악의적으로 finalizer가 수행될 수 있게 하는 것이다.

ex) 생성되다 만 객체의 하위 클래스에서 finalize가 수행되도록 한다. 그리고 의도치 않은 메서드 수행 등으로 악의적인 공격이 가능하다.

해결방법

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

finalizer와 cleaner의 적절한 사용법은?

  1. 자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할이다.
    늦게라도 회수해주는 것이 아예 안 하는 것보다는 나으니 말이다.
  2. 네이티브 피어와 연결된 객체에서다.
    native peer란 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 말한다.
    자바 객체가 아니므로 가비지 컬렉터가 회수하지 못한다.

단, 성능 저하를 감당할 수 있고 네이티브 피어가 심각한 자원을 가지고 있지 않을 때에만 해당된다.
성능 저하를 감당할 수 없거나 네이티브 피어가 사용하는 자원을 즉시 회수해야 한다면 앞서 설명한 close메서드를 사용해야 한다.

정리

지금까지 cleaner와 finalizer 사용을 하면 안되는 이유에 대해서 알아보았다.

cleaner와 finalizer는 안정망 역할로 사용하자.
또는 중요하지 않은 네이티브 자원 회수 용으로 사용하자.
그럼에도 불구하고 불확실성과 성능 저하에는 주의해야 한다.

profile
Search & Backend Engineer

0개의 댓글