제목 그대로다.
가능한 종료자 사용을 피해야 한다.
명제에 대한 설명을 하기전에 종료자가 무엇인지 알아보자.
종료자(finalizer) 는 Object 클래스에 protected 로 정의된 메소드이며 모든 클래스에서 오버로딩(재정의) 가능한 메소드이고
객체가 gc 에 의해 소멸 되기 직전에 실행되는 특징을 가지고 있다.
java.lang.Object
protected void finalize() throws Throwable { }
client override
@Overrideprotected void finalize() { }
위와 같은 특성으로 인해 객체의 사용이 종료되어 레퍼런스 참조값을 사라진다 하더라도
객체는 gc 의 대상이 될 뿐 바로 gc 가 수집하여 삭제하지 않아 실행시점이 불투명하다.
즉 예측이 불가능하고 실행 보장이 되지 않는다.
따라서 finalize 메소드 안에 객체 종료 시점에 꼭 실행되어야할 로직이 있다면 이 로직은 원하는 시점에 실행되지 않는다.
그렇기에 긴급한 작업을 종료자 안에서 처리하면 안된다.
추가로 예를 들자면 db 커넥션을 반환하는 코드나 file io 를 닫는 코드를 finalize() 메소드에 넣게 된다며 gc 가 대상을 수집하기전 까지 해당 로직이 실행되지 않기 때문에 한정된 connection 이 full 되어 오류를 내거나 file 의 경우 한 번에 열 수 있는 개수에 제한이 있어 이역서 오류를 낼 가능성이 커진다.
그리고 더욱 치명적인건 자바 명세에는 종료자가 즉시 실행되어야 한다는 문구도 없지만,
종료자가 반드시 실행되어야 한다는 문구도 없다는 점이다. 따라서 종료자가 실행되지 않은 객체가 남은 상태에서
프로그램이 끝나게 되는 일도 충분히 가능하다.
그리고 이러한 종료자의 더딘 실행(tardy finalization)은 성능상의 이슈도 발생 할 수 있다.
클래스에 종료자를 붙여 놓으면 드물게 객체 메모리 반환이 지연될 수 도 있고 프로그램 속도도 매우 느려질수 있다.
또한 종료자 안에서 예외가 발생하면 어떠한 경고 문구조차 출력되지 않아 디버깅이 어려워진다.
이렇게 종료자는 거의 모든 경우에 좋지 않다.
그렇다면 객체가 종료될때만 꼭 처리되어야 하는 로직은 어떻게 구현해야 할까?
그럴땐 명시적인 종료메서드 (예 : close() ) 를 새로 정의하고 객체가 종료되어야 될 시점에 해당 메소드 를 호출 하는 방식이 좋다
이런 명시적인 종료메서드는 클라이언트 코드에서 try-finally문 (1.7 이후 try-with-resources 대체가능)과 함께 사용되어 객체 종료를 보장한다.
언뜻 백해무익해 보이는 종료자는 무조건 사용하면 안될것 같지만 그래도 사용에 적합한 케이스가 있다.
그 하나는 클라이언트 코드에서 명시적 종료 메서드 호출을 잊을 경우 보험용으로 모체에 finalization 을 구현하여 혹시나 있을 클라이언트의 실수에 대비 하는것이며 이때 finalization 에는 경고 메시지 로그를 남겨 어떤 클라이언트에서 이런 호출 (bug) 을 하는지 알려야 한다. 그렇지만 이런 안전망을 구현하려 할 때도 추가적인 비용을 감안하여 사용여부를 신중히 선택해야한다.
다른 하나는 네이티브 피어(네이티브객체)와 연결된 객체를 다룰때이다.
※ 종료자의 추가적인 특성
종료자 연결이 자동으로 이뤄지지 않아 종료자가 선언되어 있는 클래스를 상속받은 클래스의 finalize() 메소드에는 상위 클래스의 finalize 메소드를 호출해야한다.
결론
자원 반환에 최종적 안전장치를 구현하거나 중요하지 않은 네이티브 자원을 종료시키는 것이 아니라면 종료자는 사용하지 말라야 한다.
그럼에도 불구하고 종료자를 사용해야 하는 드문 상황에는 super.finalize 호출을 잊지말자.
객체 종료시점에 실행되는 로직은 명시적인 종료 메서드에 정의하고 종료시점에 해당 메서드를 호출한다. (또는 try-finally문 활용)
댓글 없음:
댓글 쓰기