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编程思想》
