자바는 두 가지 객체 소멸자 finalizer와 cleaner를 제공한다. 그 중 finalizer는 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다. cleaner는 finalizer보다 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.
👎 1. 즉시 수행된다는 보장이 없다.
finalizer와 cleaner는 호출된 후 언제 실행될 지 알 수 없다. 즉, 제때 실행되어야 하는 작업을 절대 할 수 없다. finalizer와 cleaner의 실행 속도는 가비지 컬렉터에 달렸고, 가비지 컬렉터 구현마다 다르다.
또한, finalizer를 사용하면 인스턴스의 자원 회수가 제멋대로 지연될 수 있다. finalizer 스레드는 다른 애플리케이션 스레드보다 우선순위가 낮기 때문에 실행 기회를 제대로 얻지 못할 수 있다. cleaner는 자신을 수행할 스레드를 제어할 수 있다는 면에서 조금 낫긴 하지만, 여전히 백그라운드에서 수행되며 가비지 컬렉터에 의존하므로 즉각 수행되리라는 보장이 없다.
👎 2. 수행 여부조차 보장되지 않는다.
자바 언어 명세는 finalizer와 cleaner의 수행 시점뿐 아니라 수행 여부조차 보장하지 않는다. 접근할 수 없는 일부 객체에 딸린 종료 작업을 전혀 수행하지 못한 채 프로그램이 중단될 수 있다. 따라서, 상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존해서는 안 된다.
👎 3. 동작 중 발생하는 예외가 무시된다.
finalizer 동작 중 발생한 예외는 무시되며, 처리할 작업이 남았더라도 그 순간 종료된다. 잡지 못한 예외 때문에 해당 객체는 자칫 마무리가 덜 된 상태로 남을 수 있고, 다른 스레드가 이 훼손된 객체에 접근하게 될 수도 있다. 그나마 cleaner는 자신의 스레드를 통제하기 때문에 이런 문제는 발생하지 않는다.
👎 4. 심각한 성능 문제도 동반한다.
finalizer와 cleaner는 심각한 성능 문제도 동반한다. 이들은 가비지 컬렉터의 효율을 떨어뜨리기 때문에 뒤에서 설명할 AutoCloseable 을 사용하는 것보다 약 50배나 느린 성능을 보인다.
👎 5. 심각한 보안 문제를 일으킬 수 있다.
finalizer를 사용한 클래스는 finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수도 있다. 생성자나 직렬화 과정에서 예외가 발생하면 이 생성되다 만 객체에서 하위 클래스의 finalizer가 수행될 수 있다. 이 finalizer는 정적 필드에 자신의 참조를 할당하여 가비지 컬렉터가 수집하지 못하게 막을 수 있고, 허용되지 않았을 작업을 수행할 수 있게 된다. 이런 공격을 막기 위해서는 상속 자체를 막을 수 있는 final 클래스를 만들거나, 클래스 안에서 아무 일도 하지 않는 finalize 메서드를 만들고 final로 선언하자.
👍 AutoCloseable
그렇다면 파일이나 스레드 등 종료해야 할 자원을 담고 있는 객체의 클래스에서 어떻게 자원을 반납해야할까? 바로 AutoCloseable을 구현해주고, 클라이언트에서 인스턴스를 다 쓰고 나면 close 메서드를 호출하면 된다.
cleaner는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자. 물론 이런 경우라도 불확실성과 성능 저하에 주의해야 한다.