이번장의 핵심은...
자바의 객체 소멸자(finalizer, cleaner)는 안전망 역할이나
중요하지 않은 네이티브 자원 회수용으로만 사용하자
물론, 이런 경우라도 불확실성과 성능 저하에 주의해야한다
(가능하면 close()로 자원회수를 하는게 좋을거같다..)
자바의 2가지 객체 소멸자
1. finalizer
예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다
오동작, 낮은 성능, 이식성 문제의 원인이 되기도 한다
자바 9에서는 이미 deprecated 되었고, cleaner를 그 대안으로 소개한다
2. cleaner
finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다
C++의 파괴자(destructor) vs 자바의 객체 소멸자
자바의 객체 소멸자는 C++의 파괴자와는 다른 개념이다
C++ 파괴자는 특정 객체와 관련된 자원을 회수한다
자바에서는 접근할 수 없는 객체를 회수하는 역할은 가비지 컬렉터가 담당한다
C++ 파괴자는 비메모리 자원을 회수하는 용도로도 쓰인다
자바에서는 try-with-resources와 try-finally를 사용해 회수한다 (ITEM9)
자바의 finalizer와 cleaner
자바의 2가지 객체 소멸자는 즉시 수행된다는 보장이 없다
즉, finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다
예를 들어,
파일 닫기를 finalizer와 cleaner에 맡기면 오류가 날 수 있다
시스템이 동시에 열 수 있는 파일 개수는 한계가 있는데,
즉시 파일이 닫히지 않고 실행이 느리게 되어 파일을 계속 열어 둔다면
새로운 파일을 열지 못해 프로그램에 오류가 발생할 수 있다
이 2개의 객체 소멸자가 얼마나 빠르게 수행할지는 가비지 컬렉터 알고리즘에 달렸다
이런 굼뜬 finalizer 처리는 현업에서도 문제를 일으킨다
클래스에 finalizer를 달아두면 그 인스턴스의 자원 회수가 제멋대로 지연될 수 있다
그러다 OutOfMemoryError가 발생한다
(finalizer 대기열에서 회수되기를 기다리고 있는데,
불행히 finalizer 스레드는 다른 애플리케이션 스레드보다 우선순위가 낮아서
실행되지 못하는 경우가 발생할 수도 있다)
이런문제는 어떻게 해결하나?
딱 하나, finalizer를 사용하지 말자
한편, cleaner는 자신을 수행할 스레드를 제어할 수 있다는 면에서 조금 낫다
하지만 여전히 백그라운드에서 수행되며 가비지 컬렉터의 통제하에 있으니
즉각 수행되리라는 보장은 없다
수행 여부조차 보장하지 않는다
finalizer나 cleaner는 수행 시점뿐 아니라, 수행 여부조차 보장하지 않는다
프로그램 생애주기와 상관없는,
상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존해서는 안 된다
예를 들면,
db 같은 공유 자원의 영구 락 해제를 finalizer나 cleaner에 맡겨 놓으면 분산 시스템 전체가 서서히 멈출 수 있다
다른 여러 부작용들도 있다
finalizer 동작 중 발생한 예외는 무시되며, 처리할 작업이 남았더라도 그 순간 종료된다
cleaner를 사용하는 라이브러리는 자신의 스레드를 통제하기 때문에 이런 문제는 없다
심각한 성능 문제도 동반한다
간단한 객체를 생성하고..
가비지 컬렉터가 수거하기 까지 12ns 걸리는 반면,
cleaner나 finalizer를 사용하면 550ns 걸린다. (50배나 느리다)
하지만 안전망 형태로 사용하면 훨씬 빨라진다 (66ns)
심각한 보안 문제 발생할 수 있다
생성자나 직렬화 과정에서 예외가 발생하면,
이 생성되다만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있게 된다
final 클래스들은 그 누구도 하위 클래스를 만들 수 없으니 이 공격에서 안전하다
즉, final이 아닌 클래스를 finalzier 공격으로부터 방어하려면 아무 일도 하지 않는 finalize 메서드를 만들고 final 선언하자
finalizer나 cleaner를 대신해줄 묘안은 뭐야?
파일이나 스레드 등 종료해야 할 자원을 담고 있는 클래스에서 finalizer나 cleaner를 대신하려면,
그냥 AutoCloseable을 구현하고, 클라이언트가 인스턴스를 다 쓰고 나면 close 메서드를 호출하자
(일반적으로는 예외가 발생해도 제대로 종료되도록 try-with-resources를 사용해야한다)
finalizer나 cleaner는 그럼 대체 어디에 쓰는걸까?
1. 자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할
객체 소멸자가 즉시 호출되리라 보장은 없지만, 클라이언트가 하지 않은 자원 회수를 늦게라도 해주는 것이 아예 안하는 는 것보다는 낫다
자바 라이브러리의 일부 클래스는 안전망 역할의 finalizer를 제공
Ex) FileInputStream, FileOutputStream, ThreadPoolExcutor
2. 네이티브 피어(native peer)와 연결된 객체
네이티브 피어 : 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체
가비지 컬렉터는 이 네이티브 피어 존재를 알지 못한다
따라서 cleaner나 finalizer가 직접 나서서 처리하기에 적당한 작업이다
단, 성능 저하를 감당할 수 있고, 네이티브 피어가 심각한 자원을 가지고 있지 않을 때만 해당
감당할 수 없다면, close 메서드를 사용하자
'Java > Effective Java 3E' 카테고리의 다른 글
[이펙티브자바 3판] ITEM10. equals는 일반 규약을 지켜 재정의하라 (0) | 2020.09.11 |
---|---|
[이펙티브자바 3판] ITEM9. try-finally 보다는 try-with-resources를 사용하라 (0) | 2020.09.09 |
[이펙티브자바 3판] ITEM7. 다 쓴 객체 참조를 해제하라 (메모리 누수) (0) | 2020.09.07 |
[이펙티브자바 3판] ITEM6. 불필요한 객체 생성을 피하라 (0) | 2020.09.06 |
[이펙티브자바 3판] ITEM5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2020.09.06 |
댓글