终结器是awful不仅是因为保留问题,而且从性能角度来看也是如此。
在 Oracle JDK / OpenJDK 中,对象具有finalize
方法由以下实例支持终结器 http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/a006fa0a9e8f/src/share/classes/java/lang/ref/Finalizer.java,子类java.lang.ref.Reference
.
所有终结器都通过两个步骤在对象构造函数的末尾注册:调用从Java到VM http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/ade5be2b1758/src/share/vm/opto/parse1.cpp#l1938随后调用终结器.register() http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/a006fa0a9e8f/src/share/classes/java/lang/ref/Finalizer.java#l85。这种双重转换 Java->VM->Java 无法由 JIT 编译器内联。但最糟糕的是 Finalizer 的构造函数在全局锁 http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/a006fa0a9e8f/src/share/classes/java/lang/ref/Finalizer.java#l50! (捂脸)
终结器在内存占用方面也很糟糕:除了它们拥有的所有引用字段之外两个额外字段 http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/a006fa0a9e8f/src/share/classes/java/lang/ref/Finalizer.java#l42: next
and prev
.
幻像引用比终结器要好得多:
- 它们的构造不需要转换到 VM 并返回,并且可以内联;
- 除了继承自之外,他们没有额外的字段
java.lang.ref.Reference
;
- 没有进行全局同步。
这个基准 http://pastebin.com/X3C2MMCx比较可终结对象和 PhantomReference 支持的对象的分配速度:
Benchmark Mode Cnt Score Error Units
Finalizer.finalizable thrpt 5 2171,312 ± 1469,705 ops/ms
Finalizer.phantom thrpt 5 61280,612 ± 692,922 ops/ms
Finalizer.plain thrpt 5 225752,307 ± 7618,304 ops/ms