十月读书笔记:Effective Java(三)--避免使用finalize

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l00149133/article/details/52857904

一. 避免使用终结函数finalize
这里我们要说的是一个关键字finalize。
我想大家都知道finalize的作用,类的Finalize方法,可以告诉垃圾回收器应该执行的操作,该方法从Object类继承而来。在从堆中永久删除对象之前,垃圾回收器调用该对象的Finalize方法。
但是,我想说的是,finalize虽然会“继续执行”,但是它并不能保证被及时执行
及时的执行finalize里面的内容,是垃圾回收算法的一个重要功能,但是这个算法在不同的JVM中实现大相径庭。也就是说一个程序可能在你的JVM上跑得很顺利,但是在客户的JVM上却错误百出。
笔者提到了一个例子:
最近一位同事在调试一个长时间运行的GUI程序时,发现程序莫名其妙地因为OOM的错误而死掉。分析表明,该程序死掉的时候,其终结函数队列中,有上千个图形对象等待被回收。不幸的是,运行终结函数的线程优先级要低于该程序的其他线程,所以,图形对象被回收的速度达不到它们进入终结函数队列的速度,导致越来越多的图形对象进入队列等待,内存占用越老越多,最终OOM。
所以,我们不能依赖于终结函数来更新关键性的永久状态,比如不要试图**只在**finalize里面关闭一个文件,而应该在确定文件使用完后关闭它(当然,你可以在finalize里关闭它来实现一个安全网,防止别的程序员在使用你的类时没有调用关闭方法,迟一点关闭总比不关闭要好)。
可能有的同学会问,我能不能在finalize里面调用System.gc或者System.runFinalization方法来告知JVM执行终结方法,其实这只会增加finalize执行的可能性,但是依旧不能保证它被执行。

那终结函数有什么好处呢?
一个好处就是我们上面提到的安全网(safety net),当用户使用你的类,但是忘记调用显示的结束方法(比如InputStream的close,Timer的cancel方法等),你可以在finalize调用它们–迟一点关闭总比不关闭要好。
PS:显式的终止方法通常与try-finally结合使用,以确保终止。在finally块中调用该显式的终止方法可以保证它会被执行,即使在使用对象的时候抛出异常。

finalizer的第二种合法用处与对象的本地对等体(native peers)相关。所谓本地对等体,是指一个本地对象,普通对象通过本地方法委托给该本地对象。由于本地对等体不是一个普通对象,所以垃圾回收器并不知道它,当其Java对等体被回收时,本地对象并不能被回收。假设本地对等体不占用关键资源的话,则finalizer正是执行这项任务的合适方法。如果本地对等体占用了关键资源,则类需要定义一个显式终止方法,该方法需要执行释放关键资源的所有必要工作。显式终止方法可以是一个本地方法,或者可以调用一个本地方法。

需要特别注意的是,finalizer链并不会自动执行。如果一个类(非Object)拥有一个finalizer,并且一个子类重写了该方法,则子类的finalizer必须手工调用父类的finalizer。必须在try块中终结子类,在相应的finally块中终结父类。这就保证了即便子类的finalization过程抛出异常,父类的finalizer也会执行。

// Manual finalizer chaining  
@Override   
protected void finalize() throws Throwable {  
    try {  
        ... // Finalize subclass state  
    } finally {  
        super.finalize();  
    }  
}  

总之,除非作为安全网,或者为了终止非关键的本地资源,否则都不要使用finalizer。在这些确实需要finalizer的极少情况下,记得要调用super.finalizer。如果你将finalizer用作安全网,记得在finalizer中打印出这种非法使用。最后,如果需要在public、nonfinal的类中使用finalizer,考虑使用finalizer守护者,这样即使子类finalizer没有调用super.finalize,父类的finalizer也会执行。

猜你喜欢

转载自blog.csdn.net/l00149133/article/details/52857904