[Effective Java] item8 - finalizer와 cleaner 사용을 피하라

신민철·2023년 4월 13일
1

Effective Java

목록 보기
8/23
post-thumbnail

자바를 처음 배울 때 들었던 말은 finalizer를 그냥 쓰지 말라는 말이었다. 근데 왜?라는 궁금증은 해결하지 못한 채 그냥 넘어갔는데 책에서 이렇게 소개해주니 너무나도 좋다. 그럼 시작해보겠다!

자바에서는 두 가지 객체 소멸자가 존재하는데 finalizercleaner가 있다.

finalizer는 일반적으로 행동을 예측할 수 없고 위험할 수 있어 불필요하다고는 하는데 안쓰는 게 맞는 것 같다..

그의 대안으로 cleaner가 소개되었는데 여전히 finalizer처럼 위험하고 느리다.

C++의 소멸자(destructor)와는 개념이 다른데 소멸자는 비메모리 자원들을 회수하는 역할을 한다고 한다. 자바에서는 try-with-resources와 try-finally를 이용해 해결한다.

일반적으로 GC는 우리가 모르는 사이에 발생을 해서 회수해주지만, finalizer와 cleaner는 명시적으로 발생시킬 수 있다. 그런데 명시적으로 발생시켜도 언제 발생하는지는 예측이 불가능하다. 특히 finalizer가 말이다.

cleaner가 finalizer의 대안으로 제시되었다고 했는데 finalizer가 더 최악인 점은 뭐냐면 어떤 스레드가 실행할지 명시가 되어 있지 않다는 것이다! 그래서 우선순위에서 밀려서 GC에 의해 회수되지 못하고 프로그램이 죽어버리는 일이 자주 있다고 한다!

System.runFinalizersOnExit이나 Runtime.runFinalizersOnExit과 같은 메소드가 있는데 심각한 결함으로 실행될 가능성을 어느정도 보장해주는 이러한 것도 사용할 수가 없다..

finalizer는 심지어 실행 중에 생긴 Exception도 무시하고 처리할 작업이 남아있어도 그 순간 종료되게 된다. 잡지 못한 Exception이 어떠한 문제를 일으킬지도 알지 못하고 마무리를 못하게 된다. 그나마 이러한 점에서는 특정된 스레드가 있는 cleaner가 finalizer보다는 나은 편이다.

finalizer와 cleaner가 GC보다 성능이 좋으면 그나마 쓸 수 있는 명분이 있는데 이마저도 아니다! GC보다 적게는 5배부터 50배까지(실행 환경에 따라 다르겠지만) 책에서 느리다고 나와있다! 그냥 쓸 이유가 없다는 것이다.
그리고 finalizer 공격에 노출되어 보안 문제를 일으킬 수 있다.

직렬화 과정에서 예외가 발생하면 생성되다 만 객체에서 악의적인 하위 클래스의 finalizer가 실행되도록 한다. 이 finalizer가 정적 필드에 자신의 참조를 할당하여 GC가 수집하지 못하게 막을 수 있다. 이런 경우엔 final로 선언하거나 아무 것도 하지 않는 finalizer를 오버라이딩하여 final로 선언하자.

파일이나 스레드를 종료하려면 finalizer나 cleaner를 사용하지 않고 다른 걸 써야 하는데 그것은 바로 AutoClosable을 구현하고 close를 구현하는 것이다.


지금까지 단점만 나열했는데 그나마 책의 필자가 소개한 적절한 쓰임새를(아마도) 설명해보겠다.

첫번째는 자원의 소유자가 close를 호출하지 않는 것에 대한 안전장치이다. 하지만 이마저도 자바 라이브러리 중 FileInputStream, FileOutputStream, ThreadPoolExecutor는 안전망을 제공한다고 한다.

두번째는 네이티브 피어와 연결된 객체에서라고 하는데.. 어셈블리와 관련이 있는 것 같다..

적절한 쓰임새도 명확하지 않고 예측이 되지 않는다면.. 그냥 쓰지 않는게 맞겠다

0개의 댓글