Effective Java 第三版读书笔记——条款8:避免使用 Finalizer 和 Cleaner 机制
Finalizer 机制通常是不可预知的、危险的、不必要的。它们的使用会导致不稳定的行为,糟糕的性能和移植性问题。从 Java 9 开始,Finalizer 机制已被弃用,但仍被 Java 类库所使用。 Java 9 中 Cleaner 机制代替了 Finalizer 机制。 Cleaner 机制不如 Finalizer 机制那样危险,但仍然是不可预测、运行缓慢并且通常是不必要的。
Finalizer 和 Cleaner 机制的缺点:
-
不能保证他们能够及时执行。 从一个对象变得无法访问到 Finalizer 和 Cleaner 机制开始运行的这段时间是任意长的。 这意味着你永远不应该用 Finalizer 和 Cleaner 机制做任何时间敏感(time-critical)的事情。Java 规范不能保证 Finalizer 和 Cleaner 机制能及时运行;它甚至不能保证它们是否会运行。
-
Finalizer 机制的另一个问题是在执行 Finalizer 机制过程中,未捕获的异常会被忽略,并且该对象的 Finalizer 机制也会终止。未捕获的异常会使其他对象陷入一种损坏的状态(corrupt state)。如果另一个线程试图使用这样一个损坏的对象,可能会导致任意不确定的行为。通常情况下,未捕获的异常将终止线程并打印堆栈跟踪( stacktrace),但如果发生在Finalizer机制中,这些异常甚至不会打印警告信息。Cleaner 机制没有这个问题,因为使用 Cleaner 机制的类库可以控制其线程。
-
严重的性能损失。 通常来说,使用 finalizer 机制创建和销毁对象要慢大约 50 倍。这主要是因为 finalizer 机制会阻碍有效的垃圾收集。
-
严重的安全问题:它们会打开你的类来进行 finalizer 机制攻击。
Finalizer 和 Cleaner 机制的两个合法用途:
-
作为一个安全网(safety net),以防资源的拥有者忘记调用
close
方法。虽然不能保证 Finalizer 和 Cleaner 机制会及时运行(或者是否运行),但是将资源晚一点释放也要好过永远不释放。 -
第二种合理使用Cleaner机制的方法与本地对等类(native peers)有关。本地对等类是一个由普通对象委托的本地(非 Java)对象。由于本地对等类不是普通的 Java 对象,所以垃圾收集器并不知道它,当它的 Java 对等对象被回收时,本地对等类也不会被回收。假设性能是可以接受的,并且本地对等类没有持有关键的资源,那么 Finalizer 和 Cleaner 机制可能是这项任务的合适的工具。
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();
}
}