규칙 8. finalizer와 cleaner 사용을 피하라

finalize?

public class Object {
	/**
	 * Called by the garbage collector on an object when garbage collection
	 * determines that there are no more references to the object.
	 * ...
	 */
	protected void finalize() throws Throwable { }
}

‼️ 종료자finalizer는 예측 불가능하며, 대체로 위험하고, 일반적으로 불필요하다. …​ 어쨌든 종료자 사용은 피하는 것이 원칙이다.

  • GC가 객체에 대한 참조가 더 이상 없다고 판단할 때 GC로부터 호출된다. 하지만, 즉시 실행되리라는 보장이 전혀 없다([JLS, 12.6](https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.6)). 따라서 긴급한(time-critical) 작업을 종료자 안에서 처리하면 안 된다(e.g. finalize안에서 파일 닫기).

  • 종료자의 실행시점은 GC 알고리즘에 좌우되는데, 이 알고리즘은 JVM 구현마다 크게 다르다.

  • 종료자의 더딘 실행~tardy finalization~은 단순히 이론적인 문제가 아니다. 클래스에 종료자를 붙여 놓으면, 드문 일이지만 객체 메모리 반환이 지연될 수도 있다.

  • 종료자가 실행되지 않은 객체가 남은 상태로 프로그램이 끝나는 일도 충분히 가능하다. 그러므로 지속성이 보장되어야 하는 중요 상태 정보~critical persistent state~는 종료자로 갱신하면 안 된다.

  • System.gc()`나 `System.runFinalization() 같은 메서드는 종료자 실행 가능성을 높여주긴 하지만 보장하지 않는다.

  • System.runFinalizersOnExit(), `Runtime.runFinalizersOnExit()`는 종료자 실행을 보장하지만, 심각한 결함을 갖고 있어 이미 명세에서 deprecated 되었다.

  • 종료자를 사용하면 프로그램 성능이 심각하게 떨어진다.

  • 명시적인 종료 메서드~termination method~를 하나 정의하고, 더 이상 필요하지 않는 객체라면 클라이언트가 해당 메서드를 호출하도록 하라. 명심할 것은, 종료 여부를 객체 안에 보관해야 한다(유효하지 않은 객체임을 표시하는 private 필드 선언).

  • 명시적 종료 메서드는 보통 try-finally 문과 함께 쓰인다. 객체 종료를 보장하기 위해서다. Java1.7부터는 try-with-resources문 제공하기 때문에 finally 블록은 사용하지 않아도 된다([try-with-resources](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)).

사용하기 적합한 곳

  • 명시적 종료 메서드 호출을 잊을 경우를 대비하는 안전망~safety net~으로서의 역할.

    **종료자는 반환되지 않은 자원을 발견하게 될 경우 반드시 log를 남겨야 한다.** 클라이언트 코드에 버그가 있는 것이므로, 고치도록 알려야 하기 때문이다. (추가 비용을 감당하면서 구현할 가치가 있는지 신중하게 생각한다)
  • *네이티브 피어~native peer: 일반 자바 객체가 네이티브 메서드를 통해 기능 수행을 위임하는 네이티브 객체~*와 연결된 객체를 다룰 때.

    네이티브 피어는 일반 객체가 아니므로, 객체가 소멸되더라도 GC는 모른다(GC가 알 수 없을 뿐더라 Java peer가 반환될 때 같이 반환할 수도 없다). 네이티브 피어가 중요한 자원을 점유하고 있지 않다고 가정한다면, 종료자는 그런 객체의 반환에 걸맞다. 즉시 종료되어야 하는 자원을 포함하는 경우에는, 명시적인 종료 메서드를 클래스에 추가해야 한다.

주의할 점

  • finalizer chaining이 자동으로 이루어지지 않는다.

    종료자를 구현한 클래스를 상속받은 경우, 하위 클래스의 종료자는 상위클래스의 종료자를 명시적으로 호출해야 한다.
    ```java
    @Override
    protected void finalize() throws Throwable {
    	try {
    		...
    	} finally {
    		// 반드시 호출시키기 위해 try-finally 사용
    		super.finalize();
    	}
    }
    ```
    **더 나은 방법 - 종료 보호자 패턴**
    종료되어야 하는 객체의 클래스마다 안에 종료자를 정의하는 대신 익명 클래스를 활용하는 방법. 이 익명 클래스로 만든 객체를 *종료 보호자~finalizer guardian~*라고 한다. Foo 객체의 참조가 사라지는 순간 종료 보호자도 실행 가능한 상태가 된다.
    ```java
    // 종료 보호자 숙어(Finalizer Fuardian idiom)
    public class Foo {
    	// 이 객체는 바깥 객체(Foo)를 종료시키는 역할만 한다
    	private final Object finalizerFuardian = new Object() {
    		@Override
    		protected void finaliza() throws Throwable {
    			// 바깥 Foo 객체를 종료시킴
    		}
    	}
    }
    ```