Programming/Java

[Effective Java] item8 - finalizer와 cleaner 사용을 피하라.

VSFe 2021. 2. 5. 19:30
finalizer와 cleaner 사용을 피하라!

공부하면서 느낀거지만, 내용이 꽤나 어렵다. 같이 스터디 하는 현업자분들도 실제로 GC를 강제로 발생시키는 경우는 사실상 없다고 말했기에, 이론적인 내용이라고 생각하고 접근해보려고 한다.

본인은 자바를 처음 공부할 때 이상민씨의 "자바의 신"으로 공부를 했었는데, 거기서 finalize()를 소개하면서, 절대 사용하지 말라고 (엄밀히 말하면 java.lang에 있는 finalization()이 각각의 Object의 finalize()를 실행 시킨다는 말을 하면서) 강조했었다. 사실 초보자를 위한 책인 만큼 그냥 쓰지 말라면 쓰지 말라고 생각했는데, 이번 절에서 마침 해당 내용이 나와서 좀 좋긴 했다.

일반적인 GC는 우리가 모르는 사이에 발생하지만, finalizercleaner를 통해 수동으로도 발생시킬 수 있다. 일단 책을 보면 자바 개발자들은 finalizer를 사용하지 않기를 권고하고, (그래서 deprecated 되어있다.) cleaner를 사용하길 권하나, 사실 우리가 일부러 GC를 실행하진 않으니까...

사실 학교에서도 객체지향을 설명하면서 C++ 수업을 했었고, 자바를 공부한건 얼마 안 된 시점이었기에 메모리 관리는 new와 delete로 수동으로 관리해주는게 편했다. 그치만... 자바에서의 GC 수동 발생은 delete와는 개념이 다르다.

가장 치명적인 점이라면 finalizer와 cleaner는 우리가 생각한 시점에 발생하지 않는다는 것이다. delete나 free야 단순히 메모리 회수니 해당 상황에 바로 실행되지만, 자바의 두 메소드는 언제 발생할지 모르기 때문에 치명적이다. 가뜩이나 이 수행 시점은 GC 알고리즘 구현에 따라 다르기 때문에 JVM이 다르면 높은 확률로 다른 결과를 만들어낼 수 있다...

finalizer는 하나 더 골때리는 단점이 있는데, 어떤 스레드가 finalizer를 실행할지 명시가 되어 있지 않다! 그래서 우선순위에서 밀려서 GC되지 못하고 프로그램이 죽어버리는 일도 왕왕 있다고 한다. (cleaner는 이런 측면에선 finalizer보단 낫다.)

사실 이쯤되면 언어 명세가 잘못된 것 같긴 한데, 상황에 따라 아예 실행도 안하고 프로그램이 종료될 수 있다. (즉, 실행 여부 자체를 보장할 수 없다.)

System에는 System.gcSystem.runFinalization 메소드가 있으나, 이건 실행 가능성을 높여주는거지, 실행이 될 것을 보장하는 것이 아니다! 이쯤되면 그냥 쓰지 말라고 만든게 아닐까?

단점이 더 있는데, 짧게 쓰자면...

  • finalizer 실행 중에 발생한 예외는 catch 되지 않고 그냥 무시된다.
  • 가비지 컬렉터의 효율이 떨어져서 프로그램 성능이 급격하게 떨어진다.
  • finalizer 공격에 노출되어 보안 문제를 일으킬 수 있다.

마지막은 무엇을 의미할까? 직렬화 과정에서 예외가 발생하면 생성되다 만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있도록 한다. 한술 더 떠 자기 자신을 static으로 참조하게 함으로써 절대 GC 당하지 않을 수 있다. 이런 경우엔 final로 선언하거나, 아무것도 하지 않는 finalizer를 오버라이딩 해서 final로 선언하자.

파일이나 스레드 같은걸 종료하려면 finalizer나 cleaner를 사용하지 않고 다른걸 써야 할 텐데, 뭐가 있을까? 바로 AutoClosable을 구현하고 close만 호출해주면 된다.

흠... 아무리 봐도 왜 두 메소드가 존재하는지 알 수가 없다. 대체 뭐에 쓰라고 만들어둔걸까??

좋은 방법 하나는 close를 호출하지 않음에 대한 안전장치이다. 즉시 호출될 가능성이 낮다고 해도, 클라이언트가 실수로 호출하지 않는 것을 조금이나마 보호할 수 있다. 실제로 자바 라이브러리의 일부 클래스는 이런 형식의 finalizer를 제공한다.

또 다른 예시는 네이티브 피어와 연결된 객체에서 사용된다. 이것은 일반 자바 객체가 네이티브 메소드를 통해 기능을 위임한 네이티브 객체를 의미하는데, 자바 객체가 아니니 GC 당하지 않는다. 이러다 보니 자바 객체가 회수된다고 해서 네이티브 객체가 회수되는 것이 아니다. 다만 이런 경우엔 성능 저하가 발생할 수 있으니 앞서 설명한 Close를 사용하는 것이 나을 것이다.

cleaner도 이왕이면 사용하지 말라고 권하고 있는데, 실제로 cleaner에 대해 적힌 문서를 보면...

The behavior of cleaners during System.exit is implementation specific. No guarantees are made relating to whether cleaning actions are invoked or not.

아... 그냥 쓰지 말자.