결론 : 자바에서는 객체 회수는 가비지 컬렉터가 담당하고 비메모리 자원 회수는 try-with-recourses가 담당하므로 여러가지 문제점이 있는 finalizer와 cleaner를 굳이 사용해 객체 회수나 비메모리 자원회수를 수행할 필요가 없다.
자바에는 두가지 객체 소멸자가 존재한다 - finalizer, cleaner
1. finalizer
- 예측할 수 없고 상황에 따라 위험하다.
- 오작동, 낮은 성능, 이식성 문제의 원인
- 모든 클래스는 Object 클래스를 상속받고 Object 클래스가 해당 finalizer 메서드 소유
- 별도의 스레드 풀을 가지고 동작
- 이미 Deprecated 됨.
- 참조 해제 후(null 처리) System.gc() 호출시 자동으로 해당 메서드가 호출됨
2. Cleaner
- finalizer 보다 덜 위험하지만 여전히 예측 불가능하고, 느리고 일반적으로 불필요하다.
3. 문제점
// 문제점 1. finalizer와 cleaner는 즉시 실행된다는 보장이 없다.
// 실행되기까지 얼마나 걸릴지 예측이 불가능하고 이 때문에, 제때 실행되어야 하는 작업을 할 수 없다.
// 예를 들어, 파일 닫기를 이에 맡기면 시스템이 동시에 열 수 있는 파일의 수가 제한적이기 때문에 오류 발생
// System.gc나 System.runFinalization 과 같은 메서드는 실행될 가능성을 높여주지만 보장하진 않는다.
// System.runFinalizersOnExit, Runtime.runFinalizersOnExit가 둘의 실행을 보장하지만 메서드 자체가 결함이 많다.
// 문제점 2. finalizer와 cleaner는 수행 여부 또한 보장되지 않는다.
// 이는 접근할 수 없는 일부 객체에 딸린 작업을 전혀 수행하지 못한채 프로그램이 중단될 수 있음을 의미.
// 상태를 영구적으로 수정하는 작업에서는 절대 이를 사용하면 안된다.
// 예를 들어, 데이터베이스와 같은 공유자원에 영구 락(lock) 해제를 맡기면 큰 문제가 발생될 것이다.
// 문제점 3. 예외 처리를 안해준다.
// 메서드 안에서 예외가 발생하더라도 개발자에게 알리지 않는다.
// cleaner는 자신을 처리할 스레드를 통제하기 때문에 해당 문제는 발생하지 않는다.
// 문제점 4 : finalizer와 cleaner는 심각한 성능 문제도 동반한다.
// 기본적으로 GC과정에서 일어나는데 GC 자체가 비용이 큼.
// try-with-resources 사용해 객체 생성하고 가비지 컬렉터가 수거하는데 12ns 소요
// finalizer 사용하여 객체 생성 및 파괴가 550ns 소요
// 문제점 5 : finalizer를 사용한 클래스는 finalizer 공격에 노출되어 심각한 보안 문제를 일으킬 수 있다.
// finalizer의 공격 원리 : 생성자나 직렬화 과정에서 예외 발생시 이 생성되다 만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있다.
// 객체 생성을 막으려면 생성자에 예외를 던지는 것만으로도 충분하지만 finalizer 사용시 그렇지 않다.
// final이 아닌 클래스를 finlaizer 공격으로부터 방어하려면 아무일도 하지 않는 finalizer메서드를 만들고 final로 선언하자.
// - final이 아닌 클래스를 상속받은 클래스가 있다고 가정할 때, 부모 클래스의 finalizer메서드를 final로 처리해 자식 클래스가 해당 메서드를 사용못하게 처리로 보안
4. 자원을 담은 객체에서 소멸 방법
// 파일이나 스레드 등 종료해야할 자원을 담고있는 객체의 클래스에서 객체 소멸 방법
// - 클래스에 AutoCloseable을 구현
// - 클라이언트에서 인스턴스를 다 쓰고 close 메서드 사용
// - 예외가 발생해도 제대로 종료되도록 try-with-resources 사용
// - 각 인스턴스는 자신이 종료되었는지 추적하는 것이 좋다 따라서, 해당 객체가 더이상 유효하지 않음을 필드에 기록
// 다른 메서드에서 해당 필드를 검사해서 이미 객체가 삭제 후 호출됐다면 IllegalStateException 발생
5. finalizer나 cleaner를 사용하는 경우
// 안전망
// - 클라이언트가 close 메서드를 호출하지 않는 것에 대한 안전망
// - FileInputStream, FileOutputStream, ThreadPoolExecutor 등에서 사용
// 네이티브 피어와 연결된 객체에서 사용.
// - 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 의미
// - 가비지 컬렉터는 자바 객체가 아닌 것은 수거를 못함
// - 이러한 경우에 사용
// 1. close 사용
CountDownLatch countDownLatch3 = new CountDownLatch(1);
cleanerTest sample4 = new cleanerTest(0, countDownLatch3);
sample4.close();
// 2. try사용시 자동제거 필요가 없어짐
try(cleanerTest sample5 = new cleanerTest(0, countDownLatch3)){
System.out.println("sample 5 청소");
}
// 3. 생성자로 생성시 자동으로 run메서드가 실행되어 객체 삭제가 되어야하지만 안됨
cleanerTest sample6 = new cleanerTest(0, countDownLatch3);
코드 정리
https://github.com/mokjaemin/EffectiveJAVA
'Language > Java Plus' 카테고리의 다른 글
Effective JAVA - Item10 : equals는 일반 규약을 지켜 재정의하라. (1) | 2023.09.18 |
---|---|
Effective JAVA - Item9 : try-finally 보다는 try-with-resources를 사용하라. (0) | 2023.09.13 |
Effective JAVA - Item7 : 다 쓴 객체 참조를 해제하라 (0) | 2023.09.12 |
Effective JAVA - Item6 : 불필요한 객체 생성을 피하라. (0) | 2023.09.12 |
Effective JAVA - Item5 : 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라. (0) | 2023.06.22 |