finalizer란?
Object 클래스에서 제공하는 finalize() Method이며 자바 9 버전 이후로 Deprecated 되었습니다.
해당 메서드를 Override하여 사용하는데 가비지 컬렉터가 해당 인스턴스를 회수하기 전에 호출합니다.
finalizer의 문제점
위의 상황이 문제가 될 수 있는데 GC가 언제 회수를 할지 알 수 없습니다.
즉, finalizer와 cleaner로는 제 때 실행되어야 하는 작업은 절대 할 수 없습니다.
ex) 시스템이 열 수 있는 파일 개수가 한정되어 있는 경우
이펙티브 자바의 저자는
"예측할 수 없고 상황에 따라 위험할 수 있어 일반적으로 불필요하다" 라고 언급합니다.
finalizer 스레드는 우선순위가 낮아 실행이 지연될 수 있습니다.
심각한 성능 문제
finalizer가 가비지 컬렉터의 효율을 떨어뜨림
예시 클래스 AutoCloseable 객체를 생성하고 GC가 수거하기 까지 12ns가 걸렸지만 finalizer을 사용하면 550ns가 걸림
심각한 보안문제
생성자나 직렬화 과정에서 예외가 발생하면, 생성 되다 만 객체에서 악의적인 finalizer가 수행될 수 있음
finalizer에서 정적 필드에 자기 자신을 할당하여 GC의 수거를 막을 수 있음
이로 인해 객체 생성을 막을 수 없음 (비정상 객체 생성)
-> 아무 일도 하지 않는 finalize 메서드를 만들고 final로 선언하여 방어할 수 있음
해결책
finalizer을 사용하지 않는 것입니다.
그 대안으로 cleaner 사용을 권장하는데, 자신을 수행할 스레드를 제어할 수 있다는 면에서 조금 낫습니다.
하지만 여전히 백그라운드에서 수행되고 GC의 통제 하에 있어 즉각 수행의 보장이 없습니다.
finalizer과 cleaner은 수행 시점 뿐아니라 수행 여부조차도 보장하지 않음
접근할 수 없는 일부 객체에 딸린 종료 작업을 전혀 수행하지 못한 채 프로그램이 종료될 수 있음
상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존해서는 안됨
System.gc, Stsem.runFinalization 명시적 호출은 아래와 같은 문제가 있음
1. 비싸다
2. 가비지 컬렉터의 실행 가능성을 높혀줄 뿐 보장하는 것이 아님
3. 언제 GC가 호출될지 JVM이 제일 잘 알고 있음
AutoCloseable 인터페이스를 구현하고 close를 호출하라 (try-with-resources와 함께 사용)
public class CloseableResource implements AutoCloseable {
private BufferedReader reader;
public CloseableResource() {
InputStream input = this.getClass()
.getClassLoader()
.getResourceAsStream("file.txt");
reader = new BufferedReader(new InputStreamReader(input));
}
public String readFirstLine() throws IOException {
String firstLine = reader.readLine();
return firstLine;
}
@Override
public void close() {
try {
reader.close();
System.out.println("Closed BufferedReader in the close method");
} catch (IOException e) {
// handle exception
}
}
}
try 블럭이 끝나는 시점에 자동으로 close 메서드를 호출함
@Test
public void whenTryWResourcesExits_thenResourceClosed() throws IOException {
try (CloseableResource resource = new CloseableResource()) {
String firstLine = resource.readFirstLine();
assertEquals("baeldung.com", firstLine);
}
}
코드 출처: baeldung
위와 같은 방법은 try가 끝나는 시점에 close 메서드 호출을 확정할 수 있음
cleaner와 finalizer의 쓰임새는?
자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할
회수를 안하는 것보다 늦게라도 해주는 것이 나을 때
- 그럴만한 가치가 있는지 심사숙고해서 사용할 것
필자)
FileInputStream 클래스 확인 결과 FileCleanable 클래스를 사용해 안전망을 구축한 것을 알 수 있다
//실제 구현부
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
@SuppressWarnings("removal")
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
//안전망 구축
FileCleanable.register(fd); // open set the fd, register the cleanup
}
네이티브 피어와 연결된 객체일 때
자바 객체가 아니라면 가비지 컬렉터가 존재를 알지 못하므로 cleaner나 finalizer를 사용하기 적당함
그럼에도 성능 저하를 감당할 수 없거나 자원을 즉시 회수해야할 떄는 close 메서드를 사용해야함
참고 : A Guide to the finalize Method in Java -baeldung