为什么 finalize() 方法在 Java 9 中被弃用?

2022-09-01 14:27:50

(这个问题与为什么你会实现finize()不同?这个问题是关于Java平台的弃用,另一个问题是关于是否应该在应用程序中使用此机制。

为什么该方法在 Java 9 中不推荐使用?finalize()

是的,它可能以错误的方式使用(例如从垃圾回收中保存对象[虽然只有一次]或尝试关闭其中的一些本机资源[这比根本不关闭要好]),以及许多其他方法可能被错误地使用。

所以真的如此危险或绝对无用,以至于有必要将其踢出Java吗?finalize()


答案 1

虽然问题是关于方法的,但主题实际上是关于整个最终确定机制。这种机制不仅包括表面API,还包括关于对象生命周期的编程语言规范,以及对JVM中垃圾回收器实现的实际影响。Object.finalizeObject.finalize

关于为什么从应用程序的角度来看,为什么很难使用最终确定已经写了很多。请参阅问题“为什么你会实现 finalize()?”“Java 9 Cleaner 是否应优先于 finalization?”以及它们的答案。另见《有效Java》,Joshua Bloch第3版,第8项。

简而言之,关于使用终结器相关的问题的一些要点是:

  • 众所周知,它们很难正确编程

  • 特别是,当对象意外地(但正确)变得无法访问时,它们可能会意外运行;例如,请参阅我对此问题的回答

  • 最终确定可以很容易地破坏子类/超类关系

  • 终结器之间没有排序

  • JVM 最多调用一次给定对象的方法,即使该对象已“复活”finalize

  • 不能保证最终确定的及时性,甚至根本无法保证它将运行

  • 没有明确的登记或注销机制

以上是使用定稿的困难。鉴于上述问题清单,任何正在考虑使用定稿的人都应该重新考虑。但是,这些问题是否足以弃用 Java 平台中的终结呢?以下各节还解释了其他几个原因。

最终确定可能会使系统变得脆弱

即使您编写了正确使用终结的对象,当您的对象集成到更大的系统中时,它也可能导致问题。即使您根本不使用定版,集成到一个更大的系统中,其中某些部分使用定版,也可能导致问题。一般问题是,创建垃圾的工作线程需要与垃圾回收器保持平衡。如果垃圾回收器落后了,至少一些收集器可以“停止世界”,并进行全面收集以赶上。最终确定使这种相互作用复杂化。即使垃圾回收器跟上了应用程序线程的步伐,终结也可能会引入瓶颈并降低系统速度,或者可能导致释放资源延迟,从而导致这些资源耗尽。这是一个系统问题。即使使用定版的实际代码是正确的,在正确编程的系统中仍然可能发生问题。

(Edit 2021-09-16:这个问题描述了一个问题,即系统在低负载下工作正常,但在高负载下失败,可能是因为分配的相对速率超过了高负载下的完成速率。

定型会导致安全问题

SEI CERT Oracle Coding Standard for Java 有一个规则 MET12-J:不要使用终结器。(请注意,这是一个关于安全编码的网站。特别是,它说

不正确地使用终结器可能会导致垃圾回收就绪对象复活,并导致拒绝服务漏洞。

Oracle 的 Java SE 安全编码指南更明确地说明了使用定版时可能出现的潜在安全问题。在这种情况下,使用终结的代码不是问题。相反,攻击者可以使用终结来攻击尚未正确保护自身的敏感代码。特别是,准则7-3 / OBJECT-3部分指出,

非最终类的部分初始化实例可以通过终结器攻击进行访问。攻击者覆盖子类中的受保护方法,并尝试创建该子类的新实例。此尝试失败...但攻击者只是忽略任何异常,并等待虚拟机对部分初始化的对象执行最终确定。发生这种情况时,将调用恶意方法实现,使攻击者能够访问 ,这是对正在完成的对象的引用。尽管该对象仅部分初始化,但攻击者仍然可以在其上调用方法。finalizefinalizethis

因此,平台中最终确定机制的存在给试图编写高保证代码的程序员带来了负担。

定稿增加了规格的复杂性

Java 平台由多个规范定义,包括语言、虚拟机和类库 API 的规范。最终确定的影响在所有这些方面都分散得很薄,但它一再让人感觉到它的存在。例如,最终确定与对象创建有非常微妙的交互(这已经足够复杂了)。Java的公共API也出现了最终确定,这意味着这些API的演变(到目前为止)被要求与以前指定的行为保持兼容。不断发展这些规范使得最终确定的成本更高。

定稿增加了实施的复杂性

这主要是关于垃圾回收器。有几种垃圾回收实现,并且都需要支付实现最终确定的成本。如果不使用最终确定,这些实现可以很好地最大限度地减少运行时开销。但是,实现仍然需要存在,并且需要正确且经过良好的测试。这是一个持续的开发和维护负担。

总结

我们在其他地方已经看到,不建议程序员使用终结。但是,如果某些内容没有用处,则不一定意味着应该弃用它。以上几点说明了这样一个事实,即即使不使用最终确定,平台中机制的存在也会带来持续的规范、开发和维护成本。鉴于该机制缺乏实用性及其带来的成本,弃用它是有意义的。最终,摆脱最终确定将使每个人都受益。

在撰写本文(2019-06-04)时,还没有具体的计划来从Java中删除终结。但是,这样做的意图肯定是这样做的。我们已弃用该方法,但尚未将其标记为删除。这是程序员停止使用此机制的正式建议。人们非正式地知道,不应该使用终结,但当然有必要采取正式的步骤。此外,库类中的某些方法(例如,ZipFile.finalize)已被弃用“删除”,这意味着这些类的终结行为可能会从将来的版本中删除。最终,我们希望在 JVM 中禁用终结(可能首先可选,然后默认禁用),并在将来的某个时候实际上从垃圾回收器中删除终结实现。Object.finalizefinalize

(编辑2021-11-03:JEP 421刚刚发布,建议弃用终结删除。在撰写本文时,它处于“候选”状态,但我预计它会向前发展。此 JEP 添加的弃用是一个正式通知,表明在后续 Java 发行版中的某个时刻将删除终结。也许并不奇怪,这个答案和JEP中的材料之间存在公平的重叠,尽管JEP更精确,并且描述了我们对该主题的思维的适度演变。

(Edit 2022-04-04:JEP 421 Deprecate Finalization for Remove 已集成并交付于 JDK 18 中。


答案 2

推荐