Java:finalize方法介绍和为什么不要使用它?【存疑】
如果查看Java中的Object.java文件,其中可以重写的有5个方法:clone(), equals(), hashcode(), finalize(), toString()。我们已经讲过了equals()和hashcode()方法,toString()方法很容易理解,而这次来看finalize()方法(clone()方法以后再讲吧)。
finalize方法是Java中在对象结束之前进行清除工作的方法。那么在Java中,是如何在对象结束之前进行消除呢?我们先来看一下。
一、Java中如何对对象结束进行清除处理?
finalize()方法的文档如下:
/** * 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<font size="-2"><sup>TM</sup></font> 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 *
可以看出,finalize()方法是由Java中的垃圾收集器来调用的。当GC认为没有其他引用指向该对象时,则调用对象的finalize()方法,进行垃圾回收前的清理工作,如:显式的关闭I/O连接等。
另外,指的注意的是,内存的回收并不是在调用finalize()方法之后立即进行的。实际情况是:一旦垃圾回收器准备好释放对象占用的空间,将首先调用其finalize()方法,并且在下一次垃圾回收的动作发生之时,才会真正的回收对象所占用的内存。因此,finalize()方法的意义是,在JVM垃圾回收时刻做一些重要的清理工作。
Java语言并不保证由哪个线程调用该方法,但可以保证的是,在调用该方法前,调用的线程不会持有任何用户可见的同步锁。如果在调用finalize()方法期间出现异常,该异常会被忽略,同时该对象的finalize()方法会终止。并且,对于任何对象而言,finalize()方法最多只能被JVM调用一次。
我们知道,System.gc()是建议JVM进行垃圾清除工作(但JVM并不保证一定执行),从而强行进行终结动作。那么我们测试下代码看看finalize()方法是如何工作的,代码如下:
package com.arthur.test; public class Test { public static void main(String[] argv){ TestFinalize tf = new TestFinalize(); System.gc(); System.out.println("GC1"); tf = null; System.gc(); System.out.println("GC2"); } } class TestFinalize{ @Override protected void finalize() throws Throwable { // TODO Auto-generated method stub System.out.println("Invoke TestFinalize finalize method."); } public TestFinalize(){ System.out.println("Create a new TestFinalize."); } }
输出结果 为:
Create a new TestFinalize. GC1 GC2 Invoke TestFinalize finalize method.
在tf不为null时,使用GC1并没有效果;在将tf置为null之后,调用GC2,JVM调用finalize()方法,从而输出“Invoke TestFinalize finalize method.”。
讲完了finalize方法,那为什么不要使用它呢?
在Effective Java中有谈到:
终结方法(finalize)通常是不可预测的,也是很危险的,一般情况下是不必要的。
finalize方法的缺点:总结起来就有几点:
不保证及时执行:因为终结方法线程的优先级比其他线程要低得多,所以对象的终结速度赶不上其他线程进入队列的速度。
不保证是否执行:当一个程序终止的时候,某些已经无法访问的对象上的终结方法却根本没有被执行,这是完全有可能的。
如果未被捕获的异常在终结过程中被抛出来,那么这种异常可以被忽略,并且该对象的终结过程也会终止。未被捕获的异常会使对象处于破坏的状态,如果另一个线程企图使用这种被破坏的对象,则可能发生任何不确定的行为。而且,在终结方法中未被捕获的异常不会打印出stacktrace,甚至连警告都不会打印出来。不易发现和调试bug。
使用终结方法会有非常严重的性能损失。Effective Java中提到,作者创建和销毁一个简单对象只需要5.4ns,而增加了终结方法之后,增加到2400ns。性能损失了400多倍。
因此,不应该以来终结方法来更新重要的持久状态。
我对缺点3和4写代码测试了下。对缺点3的测试代码如下:
class TestFinalize{ @Override protected void finalize() throws Throwable { // TODO Auto-generated method stub System.out.println("Invoke TestFinalize finalize method."); throw new IllegalArgumentException("test throw exception. Arthur"); } public TestFinalize(){ System.out.println("Create a new TestFinalize."); } public static void main(String[] argv){ TestFinalize tf = new TestFinalize(); System.gc(); } }
运行结果如下:
Create a new TestFinalize.
可以看出,并没有throw IllegalArgumentException。
对于缺点4的测试代码如下:
class TestFinalize{ public static void main(String[] argv){ long startTime = System.nanoTime(); TestFinalize tf = new TestFinalize(5); tf = null; long endTime = System.nanoTime(); System.out.println("Cost nanoTime: "+(endTime - startTime)+"ns"); } private int age; public TestFinalize(int age){ this.age = age; System.out.println("Create a new TestFinalize. Age:"+this.age); } @Override protected void finalize() throws Throwable { // TODO Auto-generated method stub System.out.println("Invoke TestFinalize finalize method."); throw new IllegalArgumentException("test throw exception. Arthur"); } }
不过运行代码,在分别对finalize方法进行注释前后,运行结果均和如下差不多:
Create a new TestFinalize. Age:5 Cost nanoTime: 387059ns
也就是说,注释与否,创建和消除TestFinalize对象消耗的ns数在30w~50w ns之间,偶尔会升到80w左右。因此,此处和书中表述的有出入。书中说是对“简单对象”的创建和消除,难道不是指的这个?【此处存疑】
既然finalize方法无法确定回收资源,那么应该怎么办呢?
对于这个问题解决办法是:
1.对于资源的回收工作放到try-finally块中进行。
2.显式地定义结束的方法,如InputStream中的close()方法。
不过有个疑问,既然finalize方法这么不好,为什么还要有它呢?其实还是有一定好处的。
finalize方法的好处:
1.当对象的所有者忘记调用前面建议的显式终止方法时,终结方法(finalize)可以充当“安全网(safety net)”。虽然finalize方法并不保证及时执行,但是迟点总比没有好。即便最后没有执行,情况也不会更坏。
2.终结方法的第二种合理用途与对象的本地对等体(native peer)有关。【唔,这个现在还不懂】
另外,指的注意的一点是,“终结方法链(finalizer chaining)”并不会被自动执行。如果类(不是Object)有终结方法,子类覆盖了终结方法,子类的终结方法就必须手工调用超类的终结方法。
参考资料:
《Effective Java》
《Java编程思想》