程序员都知道“初始化”的重要性,但通常都会忘记清除的重要性。当然Java可用垃圾收集器回收不再使用的对象所占据的内存。但对于一个“特殊”的内存区域--没有使用new关键字来分配,垃圾收集器只知道释放那些由new分配的内存(垃圾收集器简介),那该如何回收这“特殊”的内存呢?
Java提供了一个被称为收尾的机制。使用这个机制,你可以定义一些特殊的操作,这些操作将在一个对象将要被垃圾回收器回收时执行。而这些操作是在finalize()方法里实现的,它在父类Object中定义:
/**
* Class Object is the root of the class hierarchy.Every class has Object as a superclass.
* All objects, including arrays, implement the methods of this class.
*/
public class Object {
/**
* 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 finalize method to dispose of system resources or to perform other cleanup.
*/
protected void finalize() throws Throwable { }
}
在理想的情况下,它的工作原理是这样的:一旦垃圾收集器准备好释放对象占用的存储空间,首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。finalize()方法的通用格式如下:
protected void finalize() throws Throwable{
//finalize code
...
}
Java允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时被调用。finalize()方法是在垃圾收集器删除对象之前对这个对象调用。
Java语言规范中不仅不保证终结方法会被及时地执行,而且根本不保证他们会被执行。当一个程序终止时,某些已经无法访问的对象上的终结方法却根本没有执行,这是完全有可能的,因此,不应该依赖终结方法来更新重要的持久状态。System.gc和System.runFinalization这两个方法增加了终结方法被执行的机会,但不保证终结方法一定被执行。还有一点,使用终结方法又一个非常严重的性能损失。
如果在类的对象中封装的资源确定需要终止,就只需提供一个显示的终止方法,并要求该类的客户端在每个实例不再有用的时候调用这个方法。显示终止方法的典型例子是InputStream、OutputStream和java.sql.Connection的close方法。
显示终止方法通常与try-finally结构结合一起使用,以确保及时终止。在finally子句中调用显示终止方法,代码如下所示:
InputStrem inputStream = new FileInputStream(new File(""));
try{
//do something
} finally{
inputStream.close()
}
从上面看finalize()方法似乎没有给我们程序带来什么好处,相反却影响程序的性能。其实不然,finalize()方法有两个用途。第一种用途是,当对象的所有者忘记调用显示终止方法时,终结方法充当“安全网”。虽然这样做并不能保证终结方法会被及时地调用,但起码比永远不释放要好。如果终结方法发现资源还未被终止,应该在日志中记录一条警告。
第二种用户是与对象的本地对等体有关。本地对等体是一个本地对象,普通对象通过本地方法(native method)委托给一个本地对象。因为本地对等体不是一个普通对象,所以垃圾收集器不会知道它,当垃圾回收时,它也不会被回收。在本地对等体没有拥有关键资源时,终结方法正是执行这项任务的最适合工具。
值得注意的一点是“终结方法链”并不会被自动执行。如果类(不是Object)有终结方法,并且子类覆盖了终结方法,子类的终结方法就必须手工调用超类的终结方法。你应该在一个try块中终结子类,并在相应的finally块中调用超类的终结方法,代码如下所示。
try{
//finalize subclass state
} finally{
super.finalize();
}
如果子类实现者覆盖了超类的终结方法,但忘记了手工调用超类的终结方法,那么超类的终结方法将永远不会被调用到。为防止这种情况发生,就需要为每个将被终结的对象创建一个附加的对象。不是将终结方法放在要求终结处理的类中,而是把终结方法放在一个匿名的类中,该匿名类的唯一用途就是终结它的外围实例(enclosing instance)。该匿名类的单个实例被称为终结方法守卫者(finalizer guardian)。外围类的每个实例都会创建这样一个守卫者。外围实例在它的私有实例域中保存着一个对其终结方法守卫者的唯一引用,因此终结方法守卫者与外围实例可以同时启动终结过程。当守卫者被结束时,它执行外围实例所期待的终结行为,就像它的终结方法是外围对象上的一个方法一样:
//Finalizer Guardian
public class Person{
//此Object的唯一目的是完成外部Person对象
private final Object finalizerGuardian = new Object(){
@Override
protected void finalize()throws Throwable{
...//完成外部Person对象
}
};
...
}
总之,除非是作为安全网,或者为了终结非关键的本地资源,否则不要使用终结方法。如果使用了终结方法,就记住一定要调用super.finalize()。记住一句话:避免使用终结方法--finalize()。