在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. OpenAuth.Net.landv分支之旅开始制作CRM系统

    OpenAuth.Net.landv分支之旅开始制作CRM系统 这个事件的由来是因为没有一个统一的会员卡平台系统,目前需要连接三家酒店会员系统,由于三家酒店使用了三种酒店管理系统,彼此之间的耦合低.三 ...

  2. IT设备服务监控的方法论

    有方法论提导,在技战术方面才不会偏离目录. 使用服务级别作为关键语,召示着承诺和责任. https://www.circonus.com/2018/06/comprehensive-container ...

  3. A - Exposition CodeForces - 6E

    题目链接:https://vjudge.net/contest/202699#problem/A 题意 给一个n个元素的序列,从中挑出最长的子序列,要求子序列中元素差的最大值不超过k.问有几个最长子序 ...

  4. [转]解决-Dmaven.multiModuleProjectDirectory system property is not set. Check $M2_HOME environment variable and mvn script match.

    来源:http://www.cnblogs.com/sprinng/p/5141233.html 1.添加M2_HOME的环境变量 2.Preference->Java->Installe ...

  5. 【原创】python 豆瓣采集

    新手今天刚学python~~~ 有点凌乱~勉强看吧 只能算是给新手看看,见谅 简单版本的 豆瓣采集美图~~~~~~ 美女天天有 有木有~~~ python 3.4 sqlite3 BeautifulS ...

  6. Apache ActiveMQ 远程代码执行漏洞 (CVE-2016-3088)案例分析

    部署ActiveMQ运行环境 在linux上部署apache-activemq-5.10.0-bin.tar.gz 通过tar -zxvf  apache-activemq-5.10.0-bin.ta ...

  7. zprofiler三板斧解决cpu占用率过高问题(转载)

    zprofiler三板斧解决cpu占用率过高问题 九居 JVM性能与调试平台   zprofiler   上周五碰到了一个线上机器cpu占用率过高的问题.问题本身比较简单,但是定位过程中动用了多个zp ...

  8. js,JQ获取短信验证码倒计时

    按钮 <a href="javasript:void(0);"onclick="settime(this);">获取手机验证码</a> ...

  9. js,JQuery实现,带筛选,搜索的select

    代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title& ...

  10. Java中常见的排序方式-快速排序(升序)

    [基本思想] 快速排序在元素较多的情况下,排序效率是相当高的.其基本思想是这样: 假设数组为int[] arr = { 49, 38, 65, 97, 76, 13, 27, 22, 26, 41, ...