在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. 使用newtonjson解决Json日期格式问题

    继承 JsonResult 方式 使用Json.Net代替最简单的方法就是使用下面的JsonNetResult 来作为 ActionResult 返回. 1) Install-Package newt ...

  2. [转] JavaScript设计模式之发布-订阅模式(观察者模式)-Part1

    <JavaScript设计模式与开发实践>读书笔记. 发布-订阅模式又叫观察者模式,它定义了对象之间的一种一对多的依赖关系.当一个对象的状态发生改变时,所有依赖它的对象都将得到通知. 例如 ...

  3. (3).NET CORE微服务 Micro-Service ---- Consul服务治理

    Consul是注册中心,服务提供者.服务提供者.服务消费者等都要注册到Consul中,这样就可以实现服务提供者.服务消费者的隔离. 除了Consul之外,还有Eureka.Zookeeper等类似软件 ...

  4. 通过awk获取netstat命令中的进程号

    需要如下: 获取进程号

  5. python manage.py runserver指定端口和ip

    python manage.py runserver 0.0.0.0:8000 在本地运行程序,python manager.py runserver打开http://127.0.0.1:5000端口 ...

  6. Codeforces 235C Cyclical Quest 字符串 SAM KMP

    原文链接https://www.cnblogs.com/zhouzhendong/p/CF235C.html 题目传送门 -  CF235C 题意 给定一个字符串 $s$ ,多组询问,每组询问的形式为 ...

  7. MySQL高级01

    MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB. 官网下载地址:http://dev.mysql.com/downloa ...

  8. sql的with as用法

    http://www.cnblogs.com/linjiqin/archive/2013/06/24/3152667.html

  9. gradle上传本地文件到远程maven库(nexus服务器)

    自定义aar-upload.gradle文件 artifacts { archives file('./build/outputs/aar/Lib_ads-baidu-debug.aar') } up ...

  10. Linux内核中Makefile、Kconfig和.config的关系(转)

    我们在编译Linux内核时,往往在Linux内核的顶层目录会执行一些命令,这里我以RK3288举例,比如:make firefly-rk3288-linux_defconfig.make menuco ...