本章的主题是创建和销毁对象:何时以及如何创建对象,何时以及如何避免创建对象,如何确保它们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。
[toc]
终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。使用终结方法会导致行为不稳定、降低性能,以及可移植性问题。当然,终结方法也有其可用之处。
当一个对象变得不可到达的时候,垃圾回收器会回收与该对象相关联的存储空间,并不需要程序员做专门的工作。
- 可用可到达状态:正常使用时
- 不可用可到达状态:过期引用被其他对象保留,不回收
- 不可用不可到达状态:被回收
终结方法缺点
1. 不能保证会被及时地执行。
- 从一个对象变得不可达开始,到它的终结方法被执行,所花费的这段时间是任意长的,因为
JVM
会延迟执行终结方法。 - 注重时间(time-critical)的任务不应该由终结方法来完成。
- 终结方法线程的优先级比该应用程序的其他线程的要低得多。
- 不应该依赖终结方法来更新重要的持久状态。
System.gc
和System.runFinalization
不保证终结方法一定会被执行
System.runFinalizersOnExit
和Runtime.runFinalizersOnExit
有致命缺陷,已被废弃。
不要被以上四个方法所诱惑。
2. 如果异常发生在终结方法之中,则不会使线程终止,并打印出栈轨迹(Stack Trace),甚至连警告都不会打印出来。
- 未被捕获的异常会使对象处于破坏状态(a corrupt state),如果另一个线程企图使用这种被破坏的对象,则可能发生任何不确定的行为。
3. 非常严重的(Severe)性能损失。
- 在作者机子上,用终结方法创建和销毁对象慢了大约 430 倍。
解决方法
提供一个显式的终止方法,并要求该类的客户端在每个实例不再有用的时候调用这个方法。
-
显式方法必须在一个私有域重记录下“该对象已经不再有效”。如果这些方法是在对象已经终止之后被调用,其他的方法就必须检查这个域,并抛出
IllegalStateException
异常。- 典型的例子是
InputStream
,OutputStream
, 和java.sql.Connection
上的close方法
。
- 典型的例子是
显式的终止方法通常与
try-finally
结构结合起来使用,以确保及时终止。
在finally
子句内部调用显式的终止方法,可用确保及时在使用对象的时候有异常抛出,该终止方法也会被执行。
// try-finally block guarantees execution of termination method
Foo foo = new Foo(...);
try {
// Do what must be done with foo
...
} finally {
foo.terminate(); // Explicit termination method
}
终结方法合法用途
充当“安全网(safety net)”
- 当对象的所有者忘记调用前面段落中建议的显式终止方法时。【迟一点释放关键资源总比永远不释放的好】
- 如果终结方法发现资源还未被终止,则应该在日志中记录一条警告,这表示客户端代码中的一个
BUG
,应该得到修复。
与对象的本地对等体(native peer)有关
- 因为本地对等体不是一个普通对象,所以垃圾回收器不会知道它,当它的Java对等体被回收的时候,它不会被回收,该类应该具有一个显式的终止方法,释放关键资源。终止方法可以是本地方法,或者它也可用调用本地方法。
最终方法链
值得注意的一点是,“最终方法链(finalizer chaining)”并不会自动执行。如果类(不是Object)有终结方法,并且子类覆盖了终结方法,子类的终结方法就必须手动调用超类的终结方法。你应该在一个try块中终结子类,并在相应的finally块中调用超类的终结方法。
这样做可以保证:即使子类的终结过程抛出异常,超类的终结方法也会得到执行。反之亦然。
终结方法守卫者(finalizer guardian)
如果子类实现者覆盖了超类的最终方法,但是忘了手动调用超类的最终方法(或者有意选择不调用),那么超类的最终方法永远也不会被调用到。
- 可以将最终方法放在一个匿名的类(见第 22 条)中,该类唯一用途就是终结它的外围实例,该匿名类的单个实例被称为最终方法守护者。
// Finalizer Guardian idiom
public 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 终结外部实例 Foo
}
};
... // Remainder omitted
}
注意,公有类
Foo
并没有终结方法(除了Object
中继承了一个无关紧要之外),所以子类的最终方法是否调用super.finalize
并不重要。对于每一个带有最终方法的非final公有类,都应该考虑使用这种方法。
总结
总之,除非是作为安全网,或者是为了终止非关键的本地资源、否则请不要使用终结方法。在这些很少见的情况下,既然使用了终结方法,就要记住调用
super.finalize
。如果用终结方法作为安全网,要记得记录终结方法的非法用法。最后,如果需要把终结方法与公有的非final
类关联起来,请考虑使用终结方法守卫者,以确保即使子类的终结方法未能调用super.finalize
,该终结方法也会被执行。