在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块中调用超类的终结方法。保证即使子类的终结过程抛出异常,超类的终结方法也会得到执行。

 @Override
protected void finalize() throws Throwable {
try {
... // 终结子类状态
} finally {
super.finalize(); // 终结父类状态
}
}

  

  终结方法守卫者

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

  举例:

 public class A {

     // 终结守卫者
private final Object finalizerGuardian = new Object() { @Override
// 终结守卫者的终结方法将被执行
protected void finalize() {
System.out.println("A finalize by the finalizerGuardian");
}
}; @Override
// 由于终结方法被子类覆盖,该终结方法并不会被执行
protected void finalize() {
System.out.println("A finalize by the finalize method");
} public static void main(String[] args) throws Exception {
B b = new B();
b = null;
System.gc();
Thread.sleep(500);
}
} class B extends A { @Override
public void finalize() {
System.out.println("B finalize by the finalize method");
} }

  结果:

 A finalize by the finalizerGuardian
B finalize by the finalize method

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

  参考资料

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

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

Java 终结方法 避免使用终结方法的更多相关文章

  1. Java实现单例模式的9种方法

    一. 什么是单例模式 因程序需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计. 二. 单例模式的特点 1. 单例模式只能有一个实例. 2. 单例类必须创建 ...

  2. java.lang.String.getBytes(String charsetName)方法实例

    java.lang.String.getBytes(String charsetName) 方法编码将此String使用指定的字符集的字节序列,并将结果存储到一个新的字节数组. 声明 以下是java. ...

  3. java解析xml的三种方法

    java解析XML的三种方法 1.SAX事件解析 package com.wzh.sax; import org.xml.sax.Attributes; import org.xml.sax.SAXE ...

  4. Java并发编程基础--基本线程方法详解

    什么是线程 线程是操作系统调度的最小单位,一个进程中可以有多个线程,这些线程可以各自的计数器,栈,局部变量,并且能够访问共享的内存变量.多线程的优势是可以提高响应时间和吞吐量. 使用多线程 一个进程正 ...

  5. java 遍历arrayList的四种方法

    package com.test; import java.util.ArrayList;import java.util.Iterator;import java.util.List; public ...

  6. java读写Properties属性文件公用方法

    Java中有个比较重要的类Properties(Java.util.Properties),主要用于读取Java的配置文件. 它提供了几个主要的方法: 1. getProperty ( String ...

  7. [java] 更好的书写equals方法-汇率换算器的实现(4)

    [java] 更好的书写equals方法-汇率换算器的实现(4) // */ // ]]>   [java] 更好的书写equals方法-汇率换算器的实现(4) Table of Content ...

  8. Effective java笔记(六),方法

    38.检查参数的有效性 绝大多数方法和构造器对于传递给它们的参数值都会有限制.如,对象引用不能为null,数组索引有范围限制等.应该在文档中指明所有这些限制,并在方法的开头处检查参数,以强制施加这些限 ...

  9. js,java,浮点数运算错误及应对方法

    js,java浮点数运算错误及应对方法 一,浮点数为什么会有运算错误 IEEE 754 标准规定了计算机程序设计环境中的二进制和十进制的浮点数自述的交换.算术格式以及方法. 现有存储介质都是2进制.2 ...

随机推荐

  1. 腾讯应用宝Android 应用加固(乐固)操作说明(转)

    此处引用腾讯云对加固的优点说明如下: 为什么应用需要加固? 若应用不做任何安全防护,极易被病毒植入.广告替换.支付渠道篡改.钓鱼.信息劫持等,严重侵害开发者的利益. 应用进行安全防护,防止应用分发后, ...

  2. keepalived高可用配置注意事项

    安装注意事项: 1.关闭防火墙或者打开防火墙vrrp协议的通过,centos6 和centos7打开方式不同 2.一定要关闭防火墙 3.配置完成后重启网卡和keepalived服务 4.日志:/var ...

  3. mysql分组(五)

    MySQL GROUP BY 语句 GROUP BY 语句根据一个或多个列对结果集进行分组. 在分组的列上我们可以使用 COUNT, SUM, AVG,等函数. GROUP BY 语法 SELECT ...

  4. 通过impala更改Kudu表属性

    开发人员可以通过更改表的属性来更改 Impala 与给定 Kudu 表相关的元数据.这些属性包括表名, Kudu 主地址列表,以及表是否由 Impala (内部)或外部管理. Rename an Im ...

  5. Vue全局API总结

    1.extend用于创建一个子类Vue,用$mount来挂载 <body> <div id="app"></div> <script> ...

  6. HTML中鼠标滚轮事件onmousewheel

    IE/Opera属于同一类型,使用attachEvent即可添加滚轮事件. /*IE注册事件*/ if(document.attachEvent){ document.attachEvent('onm ...

  7. python面试题之如何用Python输出一个斐波那契数列

    so eary! 1 a,b = 0, 1 2 while b<100: 3 print (b), 4 a, b = b, a+b 本文转载自:python黑洞网 原文链接:http://www ...

  8. LBS基站定位

    LBS基站定位(Location Based Service,简称LBS)一般应用于手机用户,它是基于位置的服务,通过电信.移动运营商的无线电通讯网络(如GSM网.CDMA网)或外部定位方式(如GPS ...

  9. 总结mysql的三种外键约束方式

    如果表A的主关键字是表B中的字段,则该字段称为表B的外键,表A称为主表,表B称为从表.外键是用来实现参照完整性的,不同的外键约束方式将可以使两张表紧密的结合起来,特别是修改或者删除的级联操作将使得日常 ...

  10. burpsuite https证书设置

    java更新.burpsuite换来换去,chrome的证书似乎失效了.重新来一边证书导入,有一些导入方法确实坑. 尝试了直接导入到受信任的机构是无效的. 两年前就因为导入到受信任的机构,又找不到导入 ...