Effective Java - 7. 종료자 사용을 피할 것

 

Rule 7. 종료자 사용을 피할 것

본 글은 이펙티브 자바 2nd를 읽고 개인적으로 학습한 내용을 복습하기 위해 작성된 글로 내용상 오류가 있을 수 있습니다. 오류가 있다면 지적 부탁드리겠습니다.

종료자(finalize)는 예측이 불가능하며, 대체로 위험하고, 일반적으로 불필요하다. 종료자를 사용하면 시스템 오류, 성능 문제, 이식성 문제가 발생할 수 있다. 그렇기 때문에 종료자 사용은 피하는 것이 원칙이다.

7.1 종료자의 단점

즉시 실행되리라는 보장이 없다. 어떤 객체에 대한 모든 참조가 사라지고 나서 종료자가 실행되기까지는 긴 시간이 걸릴 수 있다. 따라서 긴급한 작업을 종료자 안에서 처리하면 안된다. 종료자가 실행되지 않은 객체가 남은 상태로 프로그램이 끝나게 되는 일도 발생되기 때문에 지속성이 보장되어야하는 중요상태 정보는 종료자로 갱신해서는 안된다. 그리고 종료자를 사용하면 프로그램 성능이 심각하게 떨어질 수 있다.

7.2 명시적 종료 메서드

7.2.1 명시적 종료 메서드를 사용해야할 상황

파일이나 스레드 처럼 명시적으로 반환하거나 삭제해야하는 자원의 경우 객체의 클래스는 명시적 종료 메서드를 하나 정의하고, 더 이상 필요하지 않은 객체라면 클라이언트가 해당 메서드를 호출하도록 하는 것이 좋다. 기억 해야할 것은 종료 여부를 객체 안에서 보관해야하는 것인데 유효하지 않은 객체임을 표시하는 private 필드를 두고 모든 메서드 앞에 필드를 검사하는 코드를 두어, 이미 종료된 메서드를 호출하며 IllegalStatementException이 던져지도록 한다.

7.2.2 명시적 종료 메서드의 예

  • OutputStream, InputStream, java.sql.Connection에 정의된 close메서드
  • java.util.Timeer 클래스에 정의된 cancel메서드
  • java.awt 패키지에 포함된 Graphics.dispose, Window,dispose메서드
  • image.flush메서드

7.2.3 명시적 종료 메서드 호출 위치

Foo foo = new Foo();
try {
  // foo로 해야할 작업 수행
  // ...
} finally {
  foo.terminate();  // 명시적 종료 메서드 호출
}

위의 명시적 종료 메서드는 보통 try-finally문과 함께 쓰이는데 객체 종료를 보장하기 위해서이다. 객체 사용과정에서 예외가 던져지더라도 종료 메서드는 실행된다.

7.3 종료자 사용

7.3.1 명시적 종료 메서드 호출을 잊을 경우를 대비한 안전망의 역할

종료자가 언제 호출될지 알 수 없기 때문에 자원 반환이 늦어질 수 있지만 클라이언트가 명시적 종료 메서드 호출을 잊더라도 자원이 반환된다. 하지만 종료자는 그런 자원을 발견하게 될 경우 반드시 경고 메시지를 로그로 남겨야 한다.

7.3.2 네이티브 피어(peer)와 연결된 객체를 다룰때

네이티브 피어는 일반 자바 객체가 네이티브 메서드를 통해 기능 수행을 위임하는 네이티브 객체를 말한다.

네이티브는 일반 객체가 아니기 때문에 가비지 컬렉터가 알 수 없을 뿐더러 자바 측 피어 객체가 반환될 때 같이 반환할 수 없다.네이티브 피어가 중요한 자원을 점유하고 있지 않을 경우 종료자는 그런 객체 반환에 걸맞지만 그렇지 않은 경우 명시적 종료 메서드를 클래스에 추가해야한다.

7.3.3 주의할 점 : 종료자 연결(finalizer chainning)이 자동으로 이루어지지 않는다.

수동 종료자 연결

만일 Object가 아닌 어떤 클래스가 종료자를 갖고 있고 하위 클래스가 해당 메서드를 재정의하는 경우, 하위 클래스의 종료자는 상위 클래스의 종료자를 명시적으로 호출해야한다.

@Override
protected void finalize() throw Throwable {
  try {
    // 하위 클래스의 상태 종료
  } finally {
    // 상위 클래스의 상태 종료
    super.finalize();
  }
}

위의 코드와 같이 하위 클래스의 상태는 try블럭 안에서 상위 클래스의 상태는 finally 블럭 안에서 호출해야 한다. 하위 클래스의 종료 과정에서 예외가 발생하더라도 상위 클래스의 종료자는 반드시 호출되게 할 수 있다.

종료 보호자 패턴

종료되어야 하는 객체의 클래스 안에 종료자를 정의하는 대신 익명 클래스 안에 종료자를 정의하는 것이다. 이 익명 클래스의 목적은 해당 클래스의 객체를 포함하는 객체를 종료시키는 것으로 이 익명 클래스로 만든 객체는 종료 보호자라고 부르고, 종료 되어야하는 객체 안에 하나씩 넣는다.

public class Foo {

  // 이 객체는 바깥 객체를 종료시키는 역할을 수행
  private final Object finaliezerGuardian = new Object() {
    @Override
    protected void finalize() throw Throwable {
      // 바깥 Foo 객체를 종료 시킴
    }
  };

}

7.4 요약

  • 자원반환에 대한 최종적 안전장치를 구현하자.
  • 중요하지 않은 네이티브 자원을 종료시키려는 것이 아니라면 종료자는 사용하지 않는다.
  • 굳이 종료자를 사용해야한 상황이라면 super.finalize 호출을 잊지 말자.
  • 자원 반환 안전망을 구현했다면 종료자가 호출될 때마다 로그를 남기자.
  • 하위 클래스 정의가 가능한 public 클래스에 종료자를 추가하는 상황에는 종료 보호자 패턴을 고려하자.