Java 终结方法 避免使用终结方法

  在Java中,当一个对象变得不可到达时,垃圾回收器会回收与该对象相关联的存储空间。用try-finally块来回收其他的非内存资源。

  终结方法的缺点在于不能保证会被及时地执行。从一个对象变得不可到达开始,到它的终结方法被执行,所花费的这段时间是任意长的。这意味着注重时间的任务不应该由终结方法来完成。例如,用终结方法来关闭已经打开的文件是错误的,因为打开文件的描述符是一种很有限的资源。由于JVM会延迟执行终结方法,所以大量的文件会保留在打开状态,当一个程序再不能打开文件的时候,它可能会运行失败。

  及时地执行终结方法是垃圾回收算法的一个主要功能,这种算法在不同的JVM实现中会大相径庭。如果程序依赖于终结方法被执行的时间点,这个程序的行为在不同的JVM中运行的表现可能会截然不同。一个程序在自己测试用的JVM平台上运行得非常好,而在客户的JVM平台上可能根本无法运行。

  Java语言规范不仅不保证终结方法会被及时地执行,而且根本就不保证它们会被执行。当一个程序终止时,某些已经无法访问的对象上的终结方法可能根本没有被执行。所以,不应该依赖终结方法来更新重要的持久状态。例如,依赖终结方法来释放共享资源(比如数据库)上的永久锁,很容易让整个分布式系统垮掉。

  System.gc和System.runFinalization这两个方法虽然增加了终结方法被执行的机会,但是不保证一定会执行终结方法。System.runFinalizersOnExit以及其孪生兄弟Runtime.runFinalizersOnExit这两个方法虽然声称保证终结方法被执行,但是都有致命的缺陷,已经被废弃了。

  如果未被捕获的异常在终结过程中被抛出来,这种异常会被忽略,并且该对象的终结过程也会终止。换言之,如果异常发生在终结方法之中,它不会使线程终止,也不会打印出栈轨迹(Stack Trace),甚至不会打印警告。

  使用终结方法会有严重的(Severe)性能损失。也就是说,增加终结方法会使处理速度变慢。

  如果类的对象中封装的资源(例如文件或者线程)需要终止,只需提供一个显式的终止方法,并要求该类的客户端在每个实例不再有用时调用这个方法。其中,显式的终止方法必须在一个私有域中记录下“该对象已经不再有效”。如果终止方法是在对象已经终止之后被调用,其他的方法必须检查这个域,并抛出IllegalStateException异常。显式的终结方法通常与try-finally结构结合起来使用,确保及时终止。在finally子句内部调用显式的终止方法,保证即使在使用对象时抛出异常,该终止方法也会执行。

  

  终结方法有两种合法用途。

  第一种用途:当对象的所有者忘记调用显式终止方法时,终结方法可以充当“安全网(safety net)”,迟一点释放关键资源总比永远不释放要好。

  第二种用途:本地对等体(native peer)是一个本地对象(native object),普通对象通过本地方法(native method)委托给一个本地对象。因为本地对等体不是一个普通对象,所以垃圾回收器不会知道它,当它的Java对等体被回收时,它不会被回收。在本地对等体不拥有关键资源的前提下,终结方法会回收它。如果本地对等体拥有必须被及时终止的资源,该类就应该具有一个显式的终止方法。终止方法可以是本地方法,也可以调用本地方法。

  “终结方法链(finalizer chaining)”不会被自动执行。如果类(不是Object)有终结方法,并且子类覆盖了终结方法,子类的终结方法就必须手工调用超类的终结方法。在一个try块中终结子类,并在相应的finally块中调用超类的终结方法。保证即使子类的终结过程抛出异常,超类的终结方法也会得到执行。

1 @Override
2 protected void finalize() throws Throwable {
3     try {
4         ... // 终结子类状态
5     } finally {
6         super.finalize(); // 终结父类状态
7     }
8 }

   

  终结方法守卫者

  如果子类实现者覆盖了超类的终结方法,但是忘了调用超类的终结方法,那么超类的终结方法永远不会调用。为了防止此种情况出现,可以使用终结方法守卫者,即为每个将被终结的对象创建一个附加的对象,该附加对象是一个匿名类实例,将外围类的终结操作如释放资源放入该匿名类的终结方法中。外围实例在它的私有实例域中保存着一个对其终结方法守卫者的唯一引用,因此终结方法守卫者与外围实例可以同时启动终结过程。当守卫者被终结时,它执行外围实例所期望的终结行为,就好像它的终结方法是外围对象上的一个方法一样。

  举例:

 1 public class A {
 2 
 3     // 终结守卫者
 4     private final Object finalizerGuardian = new Object() {
 5 
 6         @Override
 7         // 终结守卫者的终结方法将被执行
 8         protected void finalize() {
 9             System.out.println("A finalize by the finalizerGuardian");
10         }
11     };
12 
13 
14     @Override
15     // 由于终结方法被子类覆盖,该终结方法并不会被执行
16     protected void finalize() {
17         System.out.println("A finalize by the finalize method");
18     }
19 
20 
21     public static void main(String[] args) throws Exception {
22         B b = new B();
23         b = null;
24         System.gc();
25         Thread.sleep(500);
26     }
27 }
28 
29 class B extends A {
30 
31     @Override
32     public void finalize() {
33         System.out.println("B finalize by the finalize method");
34     }
35 
36 }

  结果:

1 A finalize by the finalizerGuardian
2 B finalize by the finalize method

 

  总之,除非是作为安全网,或者是为了终止非关键的本地资源,否则请不要使用终结方法。

  参考资料

  《Effective Java 中文版 第2版》 第7条:避免使用终结方法 P24-27

  Java终结方法的使用(终结守卫者)

原文地址:https://www.cnblogs.com/WJQ2017/p/7634420.html