《Effective Java 中文版 第二版》第二章 第7条:避免使用终结方法

本章的主题是创建和销毁对象:何时以及如何创建对象,何时以及如何避免创建对象,如何确保它们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。


[toc]

终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。使用终结方法会导致行为不稳定、降低性能,以及可移植性问题。当然,终结方法也有其可用之处。

当一个对象变得不可到达的时候,垃圾回收器会回收与该对象相关联的存储空间,并不需要程序员做专门的工作。

  • 可用可到达状态:正常使用时
  • 不可用可到达状态:过期引用被其他对象保留,不回收
  • 不可用不可到达状态:被回收

终结方法缺点

1. 不能保证会被及时地执行。

  • 从一个对象变得不可达开始,到它的终结方法被执行,所花费的这段时间是任意长的,因为JVM会延迟执行终结方法。
  • 注重时间(time-critical)的任务不应该由终结方法来完成。
  • 终结方法线程的优先级比该应用程序的其他线程的要低得多。
  • 不应该依赖终结方法来更新重要的持久状态。
    System.gcSystem.runFinalization 不保证终结方法一定会被执行
    System.runFinalizersOnExitRuntime.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,该终结方法也会被执行。

你可能感兴趣的:(《Effective Java 中文版 第二版》第二章 第7条:避免使用终结方法)