Finalizers是不可预期的,通常很危险,并且基本没有必要。使用它们会引起行为怪异,性能变差,还有其他的一些小的问题。Finalizers是有一些有价值的用途的,这些我们后面会说,但是,你应该把避免使用它们作为一条准则。就Java 9而言,finalizers被摒弃了,但是Java类库仍然在使用它。Java9里面用cleaners来代替finalizers。比起finalizers而言,Cleaners没那么危险,但是仍然不可预期,慢,并且基本没有必要

C++程序员会被警告不要将Java里面的finalizers或者cleaners对等成C++里面的析构函数。在C++里面,析构函数作为构造函数的对立面是有必要的,它是回收对象相关资源的常见的方式。在Java里面,当与一个对象相关的存储资源变得不可达,垃圾收集器起就会回收它们,不需要开发人员做任何事情。C++析构函数还被用来回收一些非内存的资源。在Java里面,try -with-resources或者try - finally用来达到这一目的(Item 9)。finalizers和cleaners的一个缺点是没有什么能够保证它们能被及时的执行。一个对象变成不可达的时间和这个对象的finalizer还有cleaner被调用的时间,可能会间隔任意长的时间。这就意味着你不应该在finalizer或者cleaner里面做任何时间要求严格的事情。比如,依赖finalizer或者cleaner来关闭文件就是一个重大的错误,因为打开文件描述符是有限的资源。如果由于系统运行finalizers或者cleaners的延迟导致文件还是保持打开的状态,那么程序可能会因为它不能在打开文件而失败。

finalizers和cleaners执行的及时性主要取决于垃圾回收算法,而它却在不同的实现里面有着很大的变化。所以一个依赖finalizer或者cleaner执行的及时性的程序也会随着变化。这种程序在你测试的一个JVM上跑的贼溜,然后在某个你最重要的客户那儿推崇的JVM挂的一塌糊入。延迟finalization并不只是一个理论上的问题。为一个类提供一个finalizer会使得回收它的对象出现不可预料的延迟。一个同事调试过一个跑了很久的GUI程序,而它很神秘的抛出了OutOfMemoryError而挂掉了。分析表明,在它死掉的时候,程序中有成千上万的图形对象在finalizer的队列里面等待被finalized和回收。不幸的是,相比于其它的应用线程,finalizer线程是跑在低优先级里面的。所以在那些对象可以执行finalization的时候,它们却没有被finalized。在语言说明中没有保证说哪个线程去执行finalizers,所以除了不去使用finalizers,没有什么方便的方法可以防止这种问题。在这一点上Cleaners比finalizers要好一些,因为写这个类的作者对它的cleaner线程是有控制的,但是cleaners仍旧受控于垃圾收集器跑在背后的,所以不能保证及时清理。说明不仅不能保证finalizers和cleaners会及时的去跑;它甚至都不能保证finalizers和cleaners到底会不会跑。对于一些不可达的对象而言,程序是完全有可能甚至有可能不调用那些方法就停止了。因此,你不应该依赖finalizer或者cleaner去更新持久的状态。比如,依赖finalizer或者cleaner去释放共享资源(比如数据库)上的持久锁,是让你整个分布式系统慢慢停下来的一种好的方式。

不要被System.gc和System.runFinalization方法所蛊惑。它们可能会增加finalizers和cleaners被执行的几率,但是并不能保证它们一定会被执行。曾经有两个方法能做这个保证:System.runFinalizersOnExit还有它邪恶的双胞胎,Runtime.runFinalizersOnExit。这两个方法有致命的缺点,并且都被抛弃了十多年了。finalizers的另一个问题是,在finalization的过程中,抛出未捕捉的异常就被忽略了,而且那个对象的finalization就会被终止。未捕捉异常会让其他对象处于在错误的状态。如果其他线程尝试使用这种对象,可能会导致任意的不可确定的行为。通常情况,未捕捉异常会终止线程并且打出堆栈信息,但是如果发生在finalizer里面它就不会这样,甚至一条警告都打印不出来。Cleaners不会有这种问题,因为使用cleaner的类库对它的线程使用控制的。

使用finalizers和cleaners会带来严重的性能上的惩罚。在我的机器上,从创建一个简单的AutoCloseable对象,到使用try -with-resources关闭它,再到垃圾收集器回收它大概12纳秒。而使用finalizer会将时间增加到550纳秒。换句话而言,用finalizers创建和销毁对象大概要慢50倍。这主要因为finalizers印制了有效的垃圾回收。用Cleaners清除类的所有实例跟finalizers是差不多的(在我的机器上每个对象大约500纳秒),但是如果你将它们用作安全保障,就像下面讨论的,cleaners就会快很多。在这种情况下,在我的机器上,创建,清除和摧毁一个对象大约需要66纳秒,这意味着不过你不使用它,你要为了安全保障你要付出五分之一(不是五十五分之一)的担保。

Finalizers有严重的安全问题:面对finalizer攻击的时候它们会将你的类暴露出来。finalizer攻击背后的想法很简单:如果一个异常从构造器中或者同等于构造器的序列化中(readObject和readResolve方法)被抛出来。一个恶意的子类的finalizer就会基于本应该“中途夭折”的构造了一半的对象去运行。这个finalizer可以将这个对象引用记录在静态域里面从而阻止它被垃圾回收。一旦这种畸形的对象被记录下来,调用这个对象上的任意方法就是小事一桩了,但是这个对象首先是不应该允许它存在的。在构造器中抛出一个异常应该足够去阻止对象的形成;然而在finalizers存在的情况下,并不是这样。这种攻击会导致可怕的结果。Final的类对finalizer攻击是免疫的,因为没人能写一个final类的恶意的子类。为了保护非final的类免于这种攻击,写一个什么都不做的final类型的finalize方法

那么当面临需要终止存有资源的对象比如文件或者线程的时候,不写finalizer或者cleaner你该做什么呢?仅仅让你的类实现AutoCloseable,并且在当对象不在需要的时候,要它的客户端在每个实例中调用close方法。尽管在面临异常的情况下,通常使用try -with-resources来保证终止(Item 9)。有一个细节需要注意,这个对象必须对它是否关闭保持追踪:close方法必须将对象无效的对象记录在一个字段里面,如果对象已经被关闭了其他方法又去调用它,那些方法必须检查这个字段并且抛出IllegalStateException。

那么存不存在cleaners和finalizers的有好处的地方。它们可能会有两个合理的使用。一个是在资源的拥有者忽略了它的close方法的调用时,它可以充当安全保障的角色。尽管cleaner和finalizer即使的调用(或者根本不会调用)是没有保证的,那么如果客户端没有释放资源的时候,迟点总比永远都不要好。如果你要写这种用于安全保障的finalizer,多想想甚至要费劲的想想,这种保护付出的代价是否值得。一些java类库,比如FileInputStream,FileOutputStream,ThreadPoolExecutor,还有java.sql.Connection就有这种用于安全保障的finalizers。cleaners的第二个合理的用途是与有native peers的对象相关。native peer是native的(非java的)对象,这种对象是普通对象委托native 方法生成的。因为native peer不是正常的对象,所以当与之对等的Java对象被收集的时候,垃圾收集器不认识它,也不会回收它。cleaner或者finalizer就可能是这种问题的合适的方式,假设性能是可以接受的,并且native peer没有握着重要的资源。如果性能要求严格或者native peer所拥有的资源必须被及时回收,那么就应该像上面一样,给类中提供一个close方法。

Cleaners用起来是有点技巧的。下面是强调这个机制的一个简单的类Room。让我们假设rooms被回收之前必须先clean。Room类实现了AutoCloseable;使用cleaner的自动清除安全保障的这一事实仅仅是一个详细实现。不像finalizers,cleaners不会污染一个类的公共API。

// An autocloseable class using a cleaner as a safety net
public class Room implements AutoCloseable {private static final Cleaner cleaner = Cleaner.create();// The state of this room, shared with our cleanableprivate final State state;// Our cleanable. Cleans the room when it’s eligible for gcprivate final Cleaner.Cleanable cleanable;public Room(int numJunkPiles) {state = new State(numJunkPiles);cleanable = cleaner.register(this, state);}@Overridepublic void close() {cleanable.clean();}// Resource that requires cleaning. Must not refer to Room!private static class State implements Runnable {int numJunkPiles; // Number of junk piles in this roomState(int numJunkPiles) {this.numJunkPiles = numJunkPiles;}// Invoked by close method or cleaner@Overridepublic void run() {System.out.println("Cleaning room");numJunkPiles = 0;}}
}

这个静态的嵌套State类持有需要cleaner用来清除room的资源。在这个例子里面,它就是numJunkPiles字段,代表了房间里垃圾堆的数量的。更实际的讲,它可能是final类型的long,这个long包含了native peer的指针。State实现了Runnable,它的run方法至多被调用一次,调用它的Cleanable是我们用用Room构造器里面的cleaner将State的实例注册生成的。Run方法的调用会被下面两个情况下的某一种触发:通常情况下,它是通过调用Room的close方法从而调用Cleanable 的 clean方法去触发的。如果当Room实例处于可垃圾回收态,但是客户端没有调用close方法,cleaner会(希望它会吧)调用State.run方法。

State对象没有引用Room实例是很重要的。如果它引用了,它会创建出来一个回路,这个回路会阻止Room实例能够被垃圾回收(还有自动清除)。因此,State必须是一个静态的嵌套类,因为非静态的嵌套类包含他们外层类的引用(Item 24)。同样的使用lambda表达式是不建议的,因为他们可以轻易的得到外层对象的引用。

就像我们之前提到的,Room的cleaner只是用于安全保障。如果客户端使用try -with-resource blocks将所有Room的实例包起来,那么自动清除就不需要了。下面这个好的例子强调了这一点:

public class Adult {public static void main(String[] args) {try (Room myRoom = new Room(7)) {System.out.println("Goodbye");}}
}

如你所料,运行Adult程序会打印Goodbye,然后打印Cleaning room。但是下面这个有问题的程序呢?它永远都不会清除房间吗?

public class Teenager {public static void main(String[] args) {new Room(99);System.out.println("Peace out");}
}

你可能会期望它打印Peace out,然后打印Cleaning room,但是在我的机器上,它没有打印出Cleaning room;它仅仅存在而已。这就是我们之前所说的不可预料的情况。Cleaner文档说,“在System.exit的过程中cleaners的行为是根据特殊实现而定的。而清除工作会不会调用它不做保证。”尽管文档没这么说,但是正常程序退出都是会清除的。在我的机器上,在Teenager的main方法中添加System.gc()就足以让程序退出前打印Cleaning room,但是不能保证在你的机器上也能看到相同的行为。

总之,不要使用cleaners,或者早于Java 9不要使用cleaners,还有finalizers,除非使用它们提供安全保障或者终止不重要的native资源。尽管是那样,也要注意不可确定性还有导致的性能结果。

创建销毁对象(第八条:杜绝使用FINALIZERS和CLEANERS)相关推荐

  1. 《Effect Java》学习笔记1———创建和销毁对象

    第二章 创建和销毁对象 1.考虑用静态工厂方法代替构造器 四大优势: i. 有名称 ii. 不必在每次调用它们的时候都创建一个新的对象:   iii. 可以返回原返回类型的任何子类型的对象: JDBC ...

  2. 《Effective Java》学习笔记 第二章 创建和销毁对象

    第二章 创建和销毁对象 何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作. 1 考虑用静态工厂方法代替构造器 一般在某处获取一 ...

  3. Effective Java:创建和销毁对象

    前言: 读这本书第1条规则的时候就感觉到这是一本很好的书,可以把我们的Java功底提升一个档次,我还是比较推荐的.本博客是针对<Effective Java>这本书第2章所写的一篇读书笔记 ...

  4. 27、Python 面向对象(创建类、创建实例对象、访问属性、内置类属性、对象销毁、类的继承、方法重写、基础重载方法、运算符重载、类属性与方法、下划线双下划线)

    27Python面向对象(Python2) Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的.本章节我们将详细介绍Python的面向对象编程. ...

  5. Effective Java(1)-创建和销毁对象

    Effective Java(1)-创建和销毁对象 转载于:https://www.cnblogs.com/Johar/p/10556218.html

  6. java创建和销毁一个对象_有效的Java –创建和销毁对象

    java创建和销毁一个对象 创建和销毁对象(第2章) 这是Joshua Blochs的< 有效的Java>第2章的简短摘要.我仅包括与自己相关的项目. 静态工厂(项目1) 静态工厂与构造函 ...

  7. unity创建和销毁对象_如何创建和销毁对象

    unity创建和销毁对象 本文是我们名为" 高级Java "的学院课程的一部分. 本课程旨在帮助您最有效地使用Java. 它讨论了高级主题,包括对象创建,并发,序列化,反射等. 它 ...

  8. 有效的Java –创建和销毁对象

    创建和销毁对象(第2章) 这是Joshua Blochs的< 有效的Java>第2章的简短摘要.我仅包括与自己相关的项目. 静态工厂(项目1) 静态工厂与构造函数的一些优点: 工厂方法的名 ...

  9. [Effective Java]第二章 创建和销毁对象

    第一章      前言 略... 第二章      创建和销毁对象 1.            考虑用静态工厂方法代替构造器 创建对象方法:一是最常用的公有构造器,二是静态工厂方法.下面是一个Bool ...

最新文章

  1. 关于学习Python的一点学习总结(58->匹配对象和编组)
  2. 经常关注的、极具参考价值的网站收集(无限畅想版)
  3. linux自定义和使用 shell 环境(一)
  4. Codeforces 1054D Changing Array
  5. 多重继承_Python 和 Java 基础对比 10 —— 类的封装、继承和多态
  6. LeetCode 169. 求众数(摩尔投票)
  7. XML具有哪些特点?相对于HTML的优势
  8. 有意思的六度分割理论
  9. MySQL从入门到入魔,总结我的学习历程,给有需要的人看!
  10. Opencv求轮廓的中心点坐标
  11. 微信iOS 8.0.8正式版重磅更新啦,这些新功能超好用!!
  12. 大神F1 Plus和中兴V5s哪个好
  13. VMware Tools 安装成功无法从主机拖动文件到虚拟机
  14. 数字孪生是什么,数字孪生能干什么?一文读懂
  15. 配色那么差,还不‘哥屋恩’去看电影!
  16. mongodb 之 模糊查询
  17. 2020.7.25T1挑竹签(jz暑假训练day10)
  18. HTML 元素学习指南
  19. 为什么c语言没有输出
  20. 批量修改图片名称(去掉原名字中的中文字符和空格)

热门文章

  1. H5移动端完美实现点击复制文本的解决方法,已经自测!
  2. Lua下的ECS框架
  3. NLP 常用数据集及语料库
  4. Python中的单例模式的几种实现方式的及优化
  5. cx_Oracle.DatabaseError: DPI-1047: Cannot locate a 64-bit Oracle Client library:
  6. ionic 微信分享
  7. BFS算法之迷宫的最短路径
  8. 模板引擎ejs与html,后台模板引擎ejs与前台模板引擎artTemplate的简单介绍
  9. python字符串转成0x字节组_python高级(四)—— 文本和字节序列(编码问题)
  10. linux 硬盘坏道数据复制,linux修复磁盘坏道(本教程完全来自实例、实测,具体参数请根据个人情况修改2021.4.12)...