《Effective Java》笔记08:避免使用 Finalizer 和 Cleaner 机制
finalize
JDK8 及以下
protected void finalize() throws Throwable
以下来自 JAVA API文档
:
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写 finalize
方法,以配置系统资源或执行其他清除。
finalize
的常规协定是:当 Java™
虚拟机已确定尚未终止的任何线程无法再通过任何方法访问此对象时,将调用此方法,除非由于准备终止的其他某个对象或类的终结操作执行了某个操作。finalize
方法可以采取任何操作,其中包括再次使此对象对其他线程可用;不过,finalize
的主要目的是在不可撤消地丢弃对象之前执行清除操作。例如,表示输入/输出连接的对象的 finalize
方法可执行显式 I/O
事务,以便在永久丢弃对象之前中断连接。
Object
类的 finalize
方法执行非特殊性操作;它仅执行一些常规返回。Object
的子类可以重写此定义。
Java
编程语言不保证哪个线程将调用某个给定对象的 finalize
方法。但可以保证在调用 finalize
时,调用 finalize
的线程将不会持有任何用户可见的同步锁定。如果 finalize
方法抛出未捕获的异常,那么该异常将被忽略,并且该对象的终结操作将终止,并不会打印警告信息。
合理使用
- 当对象忘记调用资源的关闭方法,可以在日志中记录一条警告。(
FileInputStream
,FileOutputStream
,Timer
,Connection
) 本地对等体
(native peer),是一个本地对象(native object),普通对象通过本地方法(native method)委托给一个本地对象。因为本地对等体不是一个普通对象,所以垃圾回收器不会知道它,当它的 Java 对等体被回收时,它不会被回收。所以需要使用finalize
方法。如果本地对等体拥有必须及时终止的资源,那么该类就应该具有一个显式的终止方法。终止方法应该完成所有必要的工作以便释放关键的资源。终止方法可以是本地方法,或者它也可以调用本地方法。
注意
- 使用
finalize
方法有严重的性能损失。 - 如果类(不是 Object)有
finalize
方法,并且子类覆盖了这个方法,子类必须调用超类的finalize
方法。应该在一个try
块中终结子类,并在finally
块中调用超类的终结方法。 - 如果没有调用终结方法,可以防范这个问题,为每一个将被终结的对象创建一个附加对象,不是把终结方法放在要求终结处理的类中,而是把终结方法放在一个匿名的类中,该匿名类的唯一用途就是终结它的外围实例。该匿名类的单个实例被称为终结方法守卫者,外围类的每个实例都会创建这样一个守卫者。外围实例在它的私有实例域中保存着一个对其终结方法守卫者的唯一引用,因此终结方法守卫者与外围实例可以同时启动终结过程,当守卫者被终结的时候,它执行外围实例所期望的终结行为,就好像它的终结方法是外围对象上的一个方法一样:
public class Foo {
private final Object finalizerGuardian = new Object() {
@Overide
protected void finalize() throws Throwable {
...//Finallize outer Foo object
};
...//
}
}
公有类 Foo 并没有终结方法,所以子类的终结方法是否调用 super.finalize
并不重要。对于每一个带有终结方法的非 final
公有类,都应该考虑使用这种方法。
总结
除非是作为安全网,或者为了终止非关键的本地资源,否则请不要使用终结方法。
使用了就一定要记住调用super.finalize
。考虑使用终结方法守卫者。
实例
/**
* 终结方法守卫者
* @author xingle
* http://www.cnblogs.com/xingele0917/p/4330182.html
* @date 2015-3-11 下午3:49:47
*/
public class Parent {
public static void main(String[] args){
doSth();
System.gc();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void doSth() {
Child c = new Child();
System.out.println(c);
}
private final Object guardian = new Object(){
@Override
protected void finalize(){
System.out.println("执行父类中匿名内部类--终结方法守卫者,重写的 finalize()");
// 在这里调用 Parent 重写的 finalize 即可在清理子类时调用父类自己的清理方法
parentlFinalize();
}
};
protected void parentlFinalize() {
System.out.println("执行父类自身的终结方法");
}
}
class Child extends Parent {
@Override
protected void finalize() {
System.out.println("执行子类 finalize 方法,注意,这里子类并没有调用 super.finalize()");
// 由于子类(忘记或者其他原因)没有调用 super.finalize()
// 使用终结方法守卫者可以保证子类执行 finalize() 时(没有调用 super.finalize()),父类的清理方法仍旧调用
}
}
结果:
Child@782bbb7b
执行父类中匿名内部类--终结方法守卫者,重写的 finalize()
执行父类自身的终结方法
执行子类 finalize 方法,注意,这里子类并没有调用 super.finalize()
Cleaner
从 Java 9 开始,Finalizer 机制已被弃用,但仍被 Java 类库所使用。Java 9 中 Cleaner 机制代替了 Finalizer 机制。Cleaner 机制不如 Finalizer 机制那样危险,但仍然是不可预测,运行缓慢并且通常是不必要的。
两种合理使用的方式同上文。
Cleaner 机制使用起来有点棘手。下面是演示该功能的一个简单的 Room 类。假设 Room 对象必须在被回收前清理干净。Room 类实现 AutoCloseable 接口;它的自动清理安全网使用的是一个 Cleaner 机制,这仅仅是一个实现细节。与 Finalizer 机制不同,Cleaner 机制不污染一个类的公共 API:
// An autocloseable class using a cleaner as a safety net
public class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// Resource that requires cleaning. Must not refer to Room!
private static class State implements Runnable {
int numJunkPiles; // Number of junk piles in this room
State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}
// Invoked by close method or cleaner
@Override
public void run() {
System.out.println("Cleaning room");
numJunkPiles = 0;
}
}
// The state of this room, shared with our cleanable
private final State state;
// Our cleanable. Cleans the room when it’s eligible for gc
private final Cleaner.Cleanable cleanable;
public Room(int numJunkPiles) {
state = new State(numJunkPiles);
cleanable = cleaner.register(this, state);
}
@Override
public void close() {
cleanable.clean();
}
}
静态内部 State 类拥有 Cleaner 机制清理房间所需的资源。在这里,它仅仅包含 numJunkPiles 属性,它代表混乱房间的数量。更实际地说,它可能是一个 final 修饰的 long 类型的指向本地对等类的指针。State 类实现了 Runnable 接口,其 run 方法最多只能调用一次,只能被我们在 Room 构造方法中用 Cleaner 机制注册 State 实例时得到的 Cleanable 调用。对 run 方法的调用通过以下两种方法触发:通常,通过调用 Room 的 close 方法内调用 Cleanable 的 clean 方法来触发。如果在 Room 实例有资格进行垃圾回收的时候客户端没有调用 close 方法,那么 Cleaner 机制将(希望)调用 State 的 run 方法。
一个 State 实例不引用它的 Room 实例是非常重要的。如果它引用了,则创建了一个循环,阻止了 Room 实例成为垃圾收集的资格(以及自动清除)。因此,State 必须是静态的嵌内部类,因为非静态内部类包含对其宿主类的实例的引用。同样,使用 lambda 表达式也是不明智的,因为它们很容易获取对宿主类对象的引用。
就像我们之前说的,Room 的 Cleaner 机制仅仅被用作一个安全网。如果客户将所有 Room 的实例放在 try-with-resource 块中,则永远不需要自动清理。行为良好的客户端如下所示:
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 方法期间的清理行为是特定于实现的。不保证清理行为是否被调用。”虽然规范没有说明,但对于正常的程序退出也是如此。在我的机器上,将 System.gc() 方法添加到 Teenager 类的 main 方法足以让程序退出之前打印 Cleaning room,但不能保证在你的机器上会看到相同的行为。
总之,除了作为一个安全网或者终止非关键的本地资源,不要使用 Cleaner 机制,或者是在 Java 9 发布之前的 finalizers 机制。即使是这样,也要当心不确定性和性能影响。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《Effective Java》笔记08:避免使用 Finalizer 和 Cleaner 机制
文章字数:2.3k
本文作者:Bin
发布时间:2016-11-06, 23:39:33
最后更新:2019-08-06, 00:42:50
原始链接:http://coolview.github.io/2016/11/06/Effective-Java/%E3%80%8AEffective%20Java%E3%80%8B%E7%AC%94%E8%AE%B008%EF%BC%9A%E9%81%BF%E5%85%8D%E4%BD%BF%E7%94%A8%20Finalizer%20%E5%92%8C%20Cleaner%20%E6%9C%BA%E5%88%B6/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。