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

Why is the finalize() method deprecated in Java 9?

(这个问题不同于Why would you ever implement finalize()?这个问题是关于Java平台的弃用,另一个问题是关于是否应该在应用程序中使用这个机制。)

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

是的,它可能会以错误的方式使用(比如从垃圾收集中保存一个对象[尽管只有一次]或尝试关闭其中的一些本机资源[尽管总比不关闭要好])作为以及许多其他方法可能会被错误使用。

那么 finalize() 真的如此危险或完全没用以至于有必要将它踢出 Java 吗?

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

关于为什么从应用程序的角度来看难以使用终结,已经写了很多。查看问题 Why would you ever implement finalize()? and 及其答案。另请参见 Joshua Bloch 的 Effective Java,第 3 版,第 8 项。

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

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

  • 特别是,当一个对象时,它们可能 运行 意外 意外地(但正确地)无法访问;例如, 见 my answer to this question

  • 定案很容易打破subclass/superclass关系

  • 终结器之间没有顺序

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

  • 不保证最终确定的及时性或 即使它会运行

  • 没有明确的注册或注销机制

以上是finalization的使用难点。考虑到上述问题列表,任何正在考虑使用终结的人都应该重新考虑。但是这些问题是否足以在 Java 平台中弃用最终确定?以下部分解释了其他几个原因。

终结可能会使系统变得脆弱

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

(编辑 2021-09-16:this question 描述了一个系统在低负载下工作正常但在高负载下失败的问题,可能是因为分配的相对速率超过了高负载下的最终确定速率.)

定稿会导致安全问题

Java 的 SEI CERT Oracle 编码标准有一条规则 MET12-J: Do not use finalizers。 (请注意,这是一个关于安全编码的网站。)特别是,它说

Improper use of finalizers can result in resurrection of garbage-collection-ready objects and result in denial-of-service vulnerabilities.

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

Partially initialized instances of a non-final class can be accessed via a finalizer attack. The attacker overrides the protected finalize method in a subclass and attempts to create a new instance of that subclass. This attempt fails ... but the attacker simply ignores any exception and waits for the virtual machine to perform finalization on the partially initialized object. When that occurs the malicious finalize method implementation is invoked, giving the attacker access to this, a reference to the object being finalized. Although the object is only partially initialized, the attacker can still invoke methods on it....

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

定稿增加了规范的复杂性

Java 平台由多个规范定义,包括语言规范、虚拟机和 class 库 APIs。最终确定的影响在所有这些方面的影响很小,但它反复让人感觉到它的存在。例如,finalization 与对象创建有非常微妙的交互(这已经足够复杂了)。完成也出现了 Java 的 public APIs,这意味着那些 APIs 的进化(到目前为止)被要求与以前指定的行为保持兼容。最终确定的存在使这些规范的发展成本更高。

定稿增加了实施的复杂性

这主要是关于垃圾收集器的。垃圾回收的实现有好几种,都需要付出实现终结的代价。如果不使用终结,这些实现非常擅长最大限度地减少 运行 时间开销。但是,实现仍然需要存在,并且需要正确且经过良好测试。这是一个持续的发展和马生活负担。

总结

我们在别处看到不建议程序员使用终结。但是,如果某些东西没有用,并不一定意味着它应该被弃用。上面的几点说明了这样一个事实,即即使不使用终结,平台中机制的存在也会带来持续的规范、开发和维护成本。鉴于该机制缺乏实用性及其强加的成本,弃用它是有道理的。最终,摆脱终结将使每个人受益。

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

(编辑 2021-11-03:JEP 421 刚刚发布,建议弃用最终删除。在撰写本文时,它处于“候选”状态,但我希望它会向前发展。这个 JEP 添加的弃用是一个正式通知,即在随后的 Java 版本中的某个时候将删除最终确定。也许并不奇怪,这个答案和 JEP 中的 material 之间有相当多的重叠,尽管 JEP 更准确,并且描述了我们对该主题的思考的适度演变。)

(编辑 2022-04-04:JEP 421 已在 JDK 18 中集成并交付了删除的弃用最终确定。)