Effective Java:Ch2_创建销毁对象:Item7_避免使用finalize方法
Finalizer通常是不可预测的、危险的、不必要的。使用finalizer会导致不稳定的行为、低下的性能、以及可移植问题。Finalizer也有其可用之处,本文稍后会做介绍,但是作为一个首要法则,你必须避免使用finalizer。
C++程序员需要注意Java中的finalizer并不类似C++中的析构方法destructor。C++的析构方法是回收一个对象相关的资源的常用途径,是构造方法所必需的对应物;而在Java中,当对象不可达时,与之相关的存储空间就会被垃圾回收器自动回收,不需要程序员做特殊处理。C++的析构方法还被用来回收其他的非内存资源;在Java中通常使用try-finally块来达到这个目的。
finalizer的一个缺点是不能保证它会及时地运行。从一个对象变为不可达,到其finalizer被执行,可能会经过任意长时间。这意味着你不能在finalizer中执行任何注重时间的任务。【例】依靠finalizer来关闭文件就是一个严重错误,因为打开文件的描述符是一个有限资源。JVM会延迟执行finalizer,所以大量文件会被保持在打开状态,当一个程序不再能打开文件的时候,就会运行失败。
finalizer执行的及时性首先是垃圾回收算法的功能,这种算法在JVM的不同实现之间差异很大。如果程序依赖于finalizer执行的及时性,那它的行为在不同JVM上就可能变化多端。很有可能它在你测试的JVM上运行良好,但是在你的重要客户的JVM上却不幸运行失败。
延迟finalizer并不仅仅是一个理论问题。在少数情况下,为类提供finalizer会反复延迟其实例的回收。一位同事调试过一个长期运行的GUI程序,这个程序会因为OutOfMemoryError而神秘地死掉。分析表明,在程序死掉的时候,其finalizer队列里有成千上万的图形对象等待被终结和回收。不幸的是,finalizer线程比其他线程的优先级要低,所以对象的终结速度达不到其进入队列的速度。Java语言规范并不能保证哪个线程会执行finalizer,所以除非禁止使用finalizer,没有简便方法能防止这种问题。
Java语言规范不仅不保证finalizer及时执行;甚至不保证finalizer会被执行。完全有可能出现这种情况,当程序终止时,一些不可达对象的finalizer根本还没有执行。因此,你不能依赖finalizer去更新关键的持久状态。【例】依赖finalizer去释放共享资源(例如数据库)上的持久锁,很容易让整个分布式系统挂掉。
不要被System.gc和System.runFinalization方法所迷惑,他们会提高finalizer得到执行的几率,但是他们并不保证这一点。唯一声称保证finalizer执行的方法是System.runFinalizersOnExit,以及Runtime.runFinalizersOnExit。这两个方法都有致命缺陷并且都已弃用。
如果你还不相信finalizer应该避免使用,那就考虑另一个情况:如果finalization过程中抛出了一个未捕获的异常,该异常则会被忽略,并且对象的finalization会被终止。未捕获的异常会让对象处于被破坏的状态。如果另一个线程试图使用该被破坏的对象,则会导致不可预料的行为。正常情况下,未捕获的异常会终止当前线程,并打印出对战,但是如果发生在finalizer中则不会!它甚至连警告都不会打印。
还有一点:使用finalizer会导致严重的性能损失。在我的机器上,创建和销毁一个简单对象的实践大约是5.6ns,增加finalizer后时间增加到2400ns。换言之,创建和销毁带有finalizer的对象会慢430倍。
那么,如果类的对象所封装的资源(例如文件或线程)需要终结的话,应该怎么做而不用编写finalizer呢?提供一个显式的终止方法即可,要求客户端在每个实例不在有用的时候 调用该方法。值得提及的一个细节是,实例必须记录他是否已被终止:该显式的终止方法必须在一个私有字段中记录该对象已不在有效,该对象的其他方法必须坚持这个字段,若其在对象被终止后被调用,则抛出IllegalStateException。
【例】显式的终止方法的典型例子是InputStream、OutputStream和java.sql.Connection中的close方法。【例】另一个例子是java.util.Timer中的cancel方法,该方法会执行必要的状态改变,使与该Timer实例相关的线程可以优雅地终止自己。【例】java.awt中的例子包括Graphics.dispose和Windows.dispose,这两个方法由于性能不好而通常被忽视。一个相关的方法是Image.flush,它会释放与Image实例相关的所有资源,但是该实例仍然处于可用状态,如果需要的话会重新分配资源。
// Finalizer Guardian idiompublic 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} 注意,公有类Foo没有finalizer(除了从Object继承的那个无关紧要的finalizer),所以不管其子类finalizer是否调用super.finalizer都不要紧。每个拥有finalizer的非final的公有类都需要考虑这种技术。 总之,除非作为安全网,或者为了终止非关键的本地资源,否则都不要使用finalizer。在这些确实需要finalizer的极少情况下,记得要调用super.finalizer。如果你将finalizer用作安全网,记得在finalizer中打印出这种非法使用。最后,如果需要在public、nonfinal的类中使用finalizer,考虑使用finalizer守护者,这样即使子类finalizer没有调用super.finalize,父类的finalizer也会执行。