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