虽然问题是关于Object.finalize 方法的问题,但问题实际上是关于整个finalization 机制的。该机制不仅包括表面 APIObject.finalize,还包括编程语言关于对象生命周期的规范,以及对 JVM 中垃圾收集器实现的实际影响。
关于为什么从应用程序的角度难以使用最终化的文章已经写了很多。请参阅问题Why would you ever implement finalize()? 和Should Java 9 Cleaner be preferred to finalization? 及其答案。另请参阅 Joshua Bloch 的 Effective Java,第 3 版,第 8 条。
简而言之,关于使用终结器相关问题的一些要点是:
以上是使用finalization的困难。鉴于上述问题列表,任何正在考虑使用最终确定的人都应该重新考虑。但是这些问题足以在 Java 平台中弃用 finalization 吗?以下部分解释了其他几个原因。
最终化可能会使系统变得脆弱
即使你编写了一个正确使用终结的对象,当你的对象被集成到一个更大的系统中时,它也会导致问题。即使您根本不使用终结,被集成到一个更大的系统中,其中某些部分使用终结,也可能会导致问题。一般的问题是创建垃圾的工作线程需要与垃圾收集器保持平衡。如果垃圾收集器落后了,至少有一些收集器可以“停止世界”,做一次完整的收集来赶上。最终确定使这种交互变得复杂。即使垃圾收集器跟上应用程序线程的速度,终结也可能会引入瓶颈并减慢系统速度,或者会导致释放资源的延迟,从而导致这些资源耗尽。这是一个系统问题。即使使用终结的实际代码是正确的,在正确编程的系统中仍然会出现问题。
(2021-09-16 编辑:this question 描述了系统在低负载下正常工作但在高负载下失败的问题,可能是因为相对分配率超过了高负载下的最终确定率。)
最终确定会导致安全问题
SEI CERT Oracle Java 编码标准有一条规则MET12-J: Do not use finalizers。 (注意,这是一个关于安全编码的网站。)特别是,它说
终结器使用不当可能会导致垃圾收集就绪对象复活并导致拒绝服务漏洞。
Oracle 的Secure Coding Guidelines for Java SE 更明确地说明了使用最终确定可能出现的潜在安全问题。在这种情况下,使用终结的代码不是问题。相反,攻击者可以使用终结来攻击没有适当保护自己的敏感代码。特别是,Guideline 7-3 / OBJECT-3 部分声明,
可以通过终结器攻击访问非最终类的部分初始化实例。攻击者覆盖子类中受保护的finalize 方法并尝试创建该子类的新实例。这个尝试失败了……但攻击者只是忽略了任何异常并等待虚拟机对部分初始化的对象执行终结。当这种情况发生时,会调用恶意的finalize 方法实现,使攻击者可以访问this,这是对正在完成的对象的引用。虽然对象只是部分初始化,但攻击者仍然可以在其上调用方法......
因此,平台中终结机制的存在给试图编写高保证代码的程序员带来了负担。
最终确定增加了规范的复杂性
Java 平台由多个规范定义,包括语言规范、虚拟机和类库 API。最终确定的影响在所有这些方面都分散得很薄,但它反复让人感觉到它的存在。例如,finalization 与对象创建有非常微妙的交互(这已经足够复杂了)。最终确定还出现了 Java 的公共 API,这意味着这些 API 的演变(到目前为止)需要保持与先前指定的行为兼容。最终确定的存在使这些规范的发展成本更高。
最终化增加了实现的复杂性
这主要是关于垃圾收集器。有几种垃圾收集实现,都需要支付实现终结的成本。如果不使用最终化,这些实现非常适合最小化运行时开销。但是,实现仍然需要存在,并且需要正确且经过良好测试。这是一个持续的开发和维护负担。
总结
我们在其他地方看到不建议程序员使用终结处理。但是,如果某些东西没有用,并不一定意味着它应该被弃用。以上几点说明了这样一个事实,即即使不使用最终确定,平台中仅存在该机制就会产生持续的规范、开发和维护成本。鉴于该机制缺乏实用性及其带来的成本,弃用它是有道理的。最终,摆脱定稿将使每个人受益。
在撰写本文时 (2019-06-04),没有具体计划从 Java 中移除 finalization。但是,这样做肯定是有意的。我们已弃用 Object.finalize 方法,但尚未将其标记为删除。这是程序员停止使用这种机制的正式建议。非正式地知道不应该使用最终确定,但当然有必要采取正式步骤。此外,库类中的某些finalize 方法(例如ZipFile.finalize)已被“删除”弃用,这意味着这些类的终结行为可能会从未来版本中删除。最终,我们希望在 JVM 中禁用 finalization(可能首先是可选的,然后是默认情况下),并且在将来的某个时候从垃圾收集器中真正删除 finalization 实现。
(编辑 2021-11-03:JEP 421 刚刚发布,建议弃用 finalization 以进行删除。在撰写本文时,它处于“候选”状态,但我希望它会继续前进。由此添加的弃用JEP 是一个正式通知,表明将在随后的 Java 版本中删除最终确定。也许并不奇怪,这个答案和 JEP 中的材料之间存在相当大的重叠,尽管 JEP 更精确并且描述了一个适度的演变我们对这个话题的思考。)