终结方法(finalizer)通常是不可预测的,也是危险的,一般情况下是不必要的。使用终结方法会导致行为不稳定,降低性能,以及可移植性问题。但终结方法也有其可用的地方,详见下文。
在java中,终结方法一般会结合 try-finally 块来一起使用,在 finally 子句内部调用终结方法。
不要被System.gc 和 System.runFinalization 这两个方法所诱惑,它们确实增加了终结方法被执行的机会,但是它们并不保证终结方法一定被执行。唯一声称保证终结方法被执行的方法是 System.runFinalizersOnExit,已经它臭名昭著的孪生兄弟 Runtime.runFinalizersOnExit。这两个方法都有致命缺陷,已经被废弃了。
另一个概念是终止方法,典型显式的终止方法例子是:InputStream、OutputStream 和 java.sql.Connection的close 方法,还有 java.util.Timer 的 cancel 方法。另外,java.awt包括了Graphics.dispose 和 Window.dispose,以及Image.flush,这些方法由于性能不好而不为关注。
终结方法缺点一:终结方法在于不能保证会被及时的执行。从一个对象变得不可达开始,到它的终结方法被执行,所花费时间是任意长的。这意味着,注重时间(time-critical)的任务不应该由终结方法来完成。
例如,使用终结方法来关闭已经打开的文件,这是严重错误的,因为打开文件的描述符是一种很有限的资源。由于JVM会延迟执行终结方法,所以大量的文件会保留在打开状态,当一个程序不能打开文件的时候回导致运行失败。
终结方法缺点二:如果未被捕获的异常在终结过程中被抛出,那么这种异常可以被忽略,并且该对象的终结过程也会被终结。未被捕获的异常会使对象处于破坏的状态(a corrupt state),如果另一个线程企图使用该对象,则可能发生任何不确定的行为。
正常情况未捕获的异常会使线程终止并打印堆栈轨迹,但如果异常发生在终结方法中,甚至不会打印警告!!
终结方法缺点三:使用了终结方法,会导致严重的性能损失。例如,在某个机器上创建并销毁一个简单对象时间约为5.6ns,增加一个终结方法则会增加到2400ns。
终结方法第一种合法用途是:当对象所有者忘记调用前面建议的显式终止方法时,终结方法可以充当“安全网”(safety net)。
虽然这样做不能保证终结方法会被及时执行,但在客户端无法通过显式调用终止方法来正常结束操作的情况下,迟一点释放关键资源总永不释放要好(如果终结方法发现资源仍未被终止,应该在日志中记录一条警告)。
显式终止方法的实例(四个类:FileInputStream、FileOutputStream 、Connection 和 Timer)都具有终结方法,当终止方法不起作用,这些终结方法便当了安全网。
终结方法的第二种合理用途与对象的本地对等体(native peer)有关。本地对等体是一个本地对象(native object),普通对象通过本地方法委托给一个本地对象,因为本地对等体不是一个普通对象,所以垃圾回收期并不知道它。因此,在本地对等体并不拥有关键资源时,终结方法正是执行这项任务的最合适工具。
// Manual finalizer chaining
@Override
protected void finalize() throws Throwable {
try {
// Finalize subclass state
} finally {
super.finalize();
}
}
// Finalizer Guardian idiom
public class Foo {
// Sole purpose of this object is to finalize outer Foo object
private final Object finalizerGuardian = new Object(){
@Override
protected void finalize() throws Throwable {
// Finalize outer Foo object
}
};
// Remainder omitted
}
总而言之,除非是作为安全网,或者是为了终止非关键的本地资源,否则请不要使用终结方法。