8. finalizer와 cleaner 사용을 피하라

📝 아이템 8: finalizer와 cleaner 사용을 피하라

🔹 요약

finalizercleaner를 유용하게 사용할 일은 극히 드물다

  • 안전망 역할로 자원을 반납하고자 하는 경우

  • 네이티브 자원를 정리해야 하는 경우

finalizercleaner는 수행을 보장할 수 없다

  • 전적으로 GC(가비지 컬렉터) 알고리즘에 달려있으며 GC마다 다르다

  • 테스트한 JVM에선 완벽하게 동작하여도 고객의 시스템에선 다르게 동작할 가능성이 매우 높다

finalizercleaner는 우선 순위가 낮다

  • 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 구문 예시:

💡 AutoCloseable이란?

  • AutoCloseable 은 자원을 자동으로 닫을 수 있는 기능을 제공하는 인터페이스입니다. 이 인터페이스를 구현한 객체는 try-with-resources 구문에서 사용되어, 자원을 자동으로 해제할 수 있습니다.

  • AutoCloseable 인터페이스는 close() 메서드를 정의하고 있습니다. 이 메서드는 자원을 닫는 데 사용되며, 파일, 네트워크 연결, 데이터베이스 연결 등과 같은 자원 관리에 활용됩니다.

  • AutoCloseable을 구현하면, 객체가 더 이상 필요하지 않을 때 명시적으로 close() 를 호출하지 않아도 됩니다. try-with-resources 구문을 사용하면, 블록을 벗어날 때 자동으로 close() 메서드가 호출되어 자원을 정리합니다.

  • 주요 사용 예시:

    • 파일 입출력 (예: FileInputStream, BufferedReader)

    • 데이터베이스 연결 (예: Connection, Statement)

    • 사용자 입력 (예: Scanner)

  • try-with-resources 구문 예시:


🎯 중요한 점

📌 finalizer는 예측이 불가능하고, 위험하며, 불필요하다 📌 별도의 쓰레드를 사용하는 cleanerfinalizer보다 덜 위험하지만 여전히 예측 불가능하고, 느리고, 불필요하다 📌 AutoCloseable을 구현하여 자원 사용 후 명시적으로 close() 메소드를 호출하거나, try-with-resources 구문을 통해 자원을 자동으로 관리하자.


💡 코드 예제 및 설명

❌ 잘못된 예제 (finalizer를 사용한 경우)

✅ 개선된 예제 (AutoCloseable를 사용한 경우)

✅ 개선된 예제2 (AutoCloseabletry-with-resources를 함께 사용한 경우)


❗ 어려웠던 점

⚠️ AutoCloseable을 처음 접해봤다. 그런데 나도 모르는 사이에 AutoCloseable이 구현된 클래스를 사용하고 있었다.

⚠️ Java 경험이 모던하지 않아서 cleanerfinalizer라는 걸 처음 접해봤다.

⚠️ 네이티브 메소드 개념을 들어는 봤는데 특성을 몰랐다.


💭 느낀 점

💡 finalizer와 cleaner의 존재를 모르고 사용해왔는데, GC(가비지 컬렉터)가 언제 객체를 정리할지는 예측할 수 있다는 건 알았지만, 이렇게 불확실할 줄은 몰랐다.

💡 네이티브 피어처럼 GC(가비지 컬렉터)가 직접 관리하지 않는 자원도 존재하므로, 자원 해제를 반드시 고려해야 한다.

💡 자바에서도 메모리 관리의 책임은 개발자에게 있다!

Last updated