8. finalizer와 cleaner 사용을 피하라
📝 아이템 8: finalizer와 cleaner 사용을 피하라
🔹 요약
✅ finalizer
와 cleaner
를 유용하게 사용할 일은 극히 드물다
안전망 역할로 자원을 반납하고자 하는 경우
네이티브 자원를 정리해야 하는 경우
✅ finalizer
와 cleaner
는 수행을 보장할 수 없다
전적으로
GC(가비지 컬렉터)
알고리즘에 달려있으며 GC마다 다르다테스트한 JVM에선 완벽하게 동작하여도 고객의 시스템에선 다르게 동작할 가능성이 매우 높다
✅ finalizer
와 cleaner
는 우선 순위가 낮다
Finalizer 쓰레드는 우선순위가 낮아서 실행될 기회를 얻지 못 할 수도 있다
처리되지 못한 객체가 쌓일 경우
OutOfMemoryError
가 발생될 수 있다
📚 추가 개념
💡 vs C++ destructor
C++의 Destructor는 객체가 소멸될 때 호출되며, 객체와 관련된 자원을 반납하는 데 사용됩니다. C++에서는 객체가 스코프를 벗어나거나
delete
로 메모리 할당을 해제할 때 자동으로 호출됩니다.Java에서는
try-with-resources
구문이나try-finally
블록이 자원을 관리하는 역할을 합니다.AutoCloseable
인터페이스를 구현한 클래스의 경우,try-with-resources
구문을 사용하여 자원을 자동으로 닫을 수 있습니다. 자원을 해제하려면 명시적으로close()
메서드를 호출하거나,try-with-resources
블록을 사용하여 자동으로 호출되도록 합니다.C++: Destructor는 객체 소멸 시 자원을 반납하는 역할을 수행.
Java:
AutoCloseable
인터페이스와try-with-resources
를 통해 명시적 자원 관리가 이루어진다.
💡 네이티브 피어(Native Peer)란?
네이티브 피어(Native Peer) 는 Java 객체 가 아니라, 네이티브 메소드를 통해 Java 객체와 연결된 네이티브 객체 입니다. 즉, Java Application 에서 C, C++ 와 같은 네이티브 언어로 작성된 코드와 상호작용하기 위해 사용하는 객체입니다. 이 객체는 Java 가비지 컬렉터(GC) 의 추적을 받지 않기 때문에, Java 힙 에서 관리되지 않고, 자동으로 메모리 관리가 이루어지지 않습니다.
네이티브 메소드 란 Java 에서 C, C++ 로 작성된 네이티브 라이브러리의 기능을 호출하기 위한 메소드입니다. Java에서는
native
키워드를 사용하여 선언하고, 이를 통해 네이티브 코드로 정의된 메소소드를 호출할 수 있습니다. 이 메소드는 **JNI(Java Native Interface)**를 통해 연결됩니다.**GC(가비지 컬렉터)**는 Java 힙에서 관리되는 객체만 추적하고, 네이티브 피어와 같은 네이티브 객체 는 관리하지 않습니다. 따라서 네이티브 피어는 자동으로 자원 해제를 하지 않으므로, **
finalizer
**나close()
메서드를 사용하여 명시적으로 자원을 해제해야 합니다. 이때 finalizer는 Java 객체가 소멸될 때 자원을 해제하는 방법으로 사용됩니다.네이티브 메소드 finalizer
구문 예시:public class FinalizeExample { // 자원 해제 메소드드 private void releaseResource() { System.out.println("자원 해제"); } // finalize 메서드에서 자원 해제 @Override protected void finalize() throws Throwable { try { releaseResource(); // 자원 해제 } finally { super.finalize(); // 부모 클래스(Object) finalize 호출 } } public static void main(String[] args) { FinalizeExample example = new FinalizeExample(); example = null; // 객체를 null로 설정하여 가비지 컬렉터가 finalize를 호출하도록 함 System.gc(); // 강제로 가비지 컬렉터 실행 -> finalize 동작 } }
💡 AutoCloseable이란?
AutoCloseable
은 자원을 자동으로 닫을 수 있는 기능을 제공하는 인터페이스입니다. 이 인터페이스를 구현한 객체는try-with-resources
구문에서 사용되어, 자원을 자동으로 해제할 수 있습니다.AutoCloseable
인터페이스는close()
메서드를 정의하고 있습니다. 이 메서드는 자원을 닫는 데 사용되며, 파일, 네트워크 연결, 데이터베이스 연결 등과 같은 자원 관리에 활용됩니다.AutoCloseable
을 구현하면, 객체가 더 이상 필요하지 않을 때 명시적으로close()
를 호출하지 않아도 됩니다.try-with-resources
구문을 사용하면, 블록을 벗어날 때 자동으로close()
메서드가 호출되어 자원을 정리합니다.주요 사용 예시:
파일 입출력 (예:
FileInputStream
,BufferedReader
)데이터베이스 연결 (예:
Connection
,Statement
)사용자 입력 (예:
Scanner
)
try-with-resources
구문 예시:try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) { // 파일 읽기 작업 } catch (IOException e) { e.printStackTrace(); } // try-with-resources 블록을 벗어나면 자동으로 close() 호출
🎯 중요한 점
📌 finalizer
는 예측이 불가능하고, 위험하며, 불필요하다
📌 별도의 쓰레드를 사용하는 cleaner
도 finalizer
보다 덜 위험하지만 여전히 예측 불가능하고, 느리고, 불필요하다
📌 AutoCloseable
을 구현하여 자원 사용 후 명시적으로 close() 메소드를 호출하거나, try-with-resources
구문을 통해 자원을 자동으로 관리하자.
💡 코드 예제 및 설명
❌ 잘못된 예제 (finalizer
를 사용한 경우)
finalizer
를 사용한 경우)public class SampleRunner {
public static void main(String[] args) {
SampleRunner runner = new SampleRunner();
runner.run(); // run() 메소드를 호출하여 FinalizerExample 객체를 생성하고 사용
Thread.sleep(1000); // 1초후 "Clean up" 출력될 지 아무도 모른다.
// run() 메서드가 종료되면 finalizerExample 객체는 더 이상 참조되지 않아 GC의 대상이 됨
// 그러나 GC와 finalize() 메서드 호출 시점은 JVM에 의해 결정되므로 실행이 보장되지 않음
}
private void run() {
FinalizerExample finalizerExample = new FinalizerExample();
finalizerExample.hello();
// 이 메서드가 종료되면 finalizerExample 변수는 스코프를 벗어나 참조가 끊어짐
// 이제 이 객체는 GC의 대상이 됨
}
}
public class FinalizerExample {
@Override
protected void finalize() throws Throwable {
System.out.println("Clean up");
}
public void hello() {
System.out.println("Hello");
}
}
✅ 개선된 예제 (AutoCloseable
를 사용한 경우)
AutoCloseable
를 사용한 경우)public static void main(String[] args) {
SampleResource sampleResource = null;
try {
sampleResource = new SampleResource();
sampleResource.hello();
} finally {
// finally 블록은 예외 발생 여부와 관계없이 항상 실행됨
if (sampleResource != null) {
sampleResource.close();
}
}
// try-with-resources 구문이 도입되기 전에 주로 사용되던 방식
}
// AutoCloseable 인터페이스를 구현한 클래스
public class SampleResource implements AutoCloseable {
@Override
public void close() throws RuntimeException {
// 자원을 해제하는 메소드
System.out.println("close");
}
public void hello() {
System.out.println("Hello");
}
}
✅ 개선된 예제2 (AutoCloseable
와 try-with-resources
를 함께 사용한 경우)
AutoCloseable
와 try-with-resources
를 함께 사용한 경우)public static void main(String[] args) {
// 블록이 종료되면 자동으로 close() 메서드가 호출됨
// 예외가 발생하더라도 close()는 반드시 호출됨
try(SampleResource sampleResource = new SampleResource()) {
sampleResource.hello();
}
}
// AutoCloseable 인터페이스를 구현한 클래스
public class SampleResource implements AutoCloseable {
@Override
public void close() throws RuntimeException {
// 자원을 해제하는 메소드
System.out.println("close");
}
public void hello() {
System.out.println("Hello");
}
}
❗ 어려웠던 점
⚠️ AutoCloseable
을 처음 접해봤다. 그런데 나도 모르는 사이에 AutoCloseable
이 구현된 클래스를 사용하고 있었다.
⚠️ Java 경험이 모던하지 않아서 cleaner
와 finalizer
라는 걸 처음 접해봤다.
⚠️ 네이티브 메소드
개념을 들어는 봤는데 특성을 몰랐다.
💭 느낀 점
💡 finalizer와 cleaner
의 존재를 모르고 사용해왔는데, GC(가비지 컬렉터)
가 언제 객체를 정리할지는 예측할 수 있다는 건 알았지만, 이렇게 불확실할 줄은 몰랐다.
💡 네이티브 피어
처럼 GC(가비지 컬렉터)
가 직접 관리하지 않는 자원도 존재하므로, 자원 해제를 반드시 고려해야 한다.
💡 자바에서도 메모리 관리의 책임은 개발자에게 있다!
Last updated