JAVA/Effective Java

규칙7 종료자 사용을 피해라

소농배 2018. 12. 19. 20:25
이펙티브 자바 Effective Java 2/E
국내도서
저자 : 조슈아 블로크(Joshua Bloch) / 이병준역
출판 : 인사이트 2014.09.01
상세보기

종료자(finalizer)는 예측 불가능하며, 대체로 위험하고, 일반적으로 불필요하다.

종료자가 뭔지 부터 알아보자.

/** * Called by the garbage collector on an object when garbage collection * determines that there are no more references to the object. * A subclass overrides the {@code finalize} method to dispose of * system resources or to perform other cleanup. * <p> * The general contract of {@code finalize} is that it is invoked * if and when the Java&trade; virtual * machine has determined that there is no longer any * means by which this object can be accessed by any thread that has * not yet died, except as a result of an action taken by the * finalization of some other object or class which is ready to be * finalized. The {@code finalize} method may take any action, including * making this object available again to other threads; the usual purpose * of {@code finalize}, however, is to perform cleanup actions before * the object is irrevocably discarded. For example, the finalize method * for an object that represents an input/output connection might perform * explicit I/O transactions to break the connection before the object is * permanently discarded. * <p> * The {@code finalize} method of class {@code Object} performs no * special action; it simply returns normally. Subclasses of * {@code Object} may override this definition. * <p> * The Java programming language does not guarantee which thread will * invoke the {@code finalize} method for any given object. It is * guaranteed, however, that the thread that invokes finalize will not * be holding any user-visible synchronization locks when finalize is * invoked. If an uncaught exception is thrown by the finalize method, * the exception is ignored and finalization of that object terminates. * <p> * After the {@code finalize} method has been invoked for an object, no * further action is taken until the Java virtual machine has again * determined that there is no longer any means by which this object can * be accessed by any thread that has not yet died, including possible * actions by other objects or classes which are ready to be finalized, * at which point the object may be discarded. * <p> * The {@code finalize} method is never invoked more than once by a Java * virtual machine for any given object. * <p> * Any exception thrown by the {@code finalize} method causes * the finalization of this object to be halted, but is otherwise * ignored. * * @throws Throwable the {@code Exception} raised by this method * @see java.lang.ref.WeakReference * @see java.lang.ref.PhantomReference * @jls 12.6 Finalization of Class Instances */ protected void finalize() throws Throwable { }

public class object 에는 finalize() 가 정의되어 있고 모든 클래스는 override 해서 사용할 수 있다.
finalize 메소드는 garbage collection 되기 전에 호출되기 때문에 subclass 들은 시스템 리소스를 반환할때 finalize 메소드를 사용할 수 있다.

리소스를 사용하는 클래스에서 사용하면 편할 것 같은데 왜 사용하지 말라는지 읽어보자.

사용하면 안되는 이유 1) 즉시 실행되리라는 보장이 젼혀 없다.
=> JVM 은 finalizer 메소드를 천천히 실행 시킬 수 있다. 만약에 종료자에서 파일 리소스를 닫는 명령어를 사용한다면 JVM 이 종료자를 천천히 실행하기 때문에 유한한 파일 리소스 사용에 제한이 걸릴 수 있다.

사용하면 안되는 이유 2) 클래스에 종료자를 붙여 놓으면, 객체 메모리 반환이 지연될 수 있다.
=> 작가의 경험에 따르면 종료자 큐에 객체들이 반환되기를 기다리고 있는 상태에서 다른 응용프로그램 스레드 우선순위에 밀려서 OutOfMemoryError 가 발생하였다고 한다.

사용하면 안되는 이유 3) 성능이 심각하게 떨어진다
=> 작가의 컴퓨터에서 간단한 객체를 만들고 삭제하는 데는 5.6ns면 충분한데, 종료자를 붙이자 2,400ns 로 430배 가량 느려졌다고 한다.

그럼 종료자를 대체하는 방법에는 뭐가 있을까?

종료자를 대체하는 방법 1) 명시적인 종료 메서드(termination method)를 정의한다.
=> 선언한 종료 메서드를 클라이언트가 호출하도록 한다. 이 때 종료 여부를 객체 안에 private 필드로 보관해야 한다. 모든 메서드 앞에 종료 여부를 확인하는 코드를 두어 종료된 객체가 호출되면 
IllegalStateException 을 던진다.
 이런 명시적 종료 메서드는 try-finally  문과 함께 쓰인다. 객체 종료를 보장하기 위해서다
(자바 1.7 부터는 try-with-resources 문을 지원한다. 유용할 것 같으니 한번 찾아보자.)

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

그렇다면 종료자를 어디에 써먹어야 하는 것인가.

종료자 써먹을 곳 1) 명시적 종료 메서드 호출을 잊을 경우에 안전망으로서의 역할
=> 클라이언트가 명시적 종료 메서드를 호출하지 않은 경우 종료자를 이용해서 자원을 반환하면 보험 역할을 충분히 해 낼 수 있다. 하지만 이때 꼭 로그를 남겨서 클라이언트에서 명시적 종료 메서드를 호출 할 수 있도록 한다.

종료자 써먹을 곳2) 네이티브 피어(native peer)와 연결된 객체를 다룰 때