【问题标题】:Why is the finalize() method deprecated in Java 9?为什么 Java 9 中不推荐使用 finalize() 方法?
【发布时间】:2019-10-02 00:46:24
【问题描述】:

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

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

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

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

【问题讨论】:

  • 另请参阅 OpenJDK 错误链接中的讨论:bugs.openjdk.java.net/browse/JDK-8165641
  • 简短回答:是的。它比人们想象的更无用,即使对于非常了解它的专家来说也很危险(例如,除非你知道什么是可达性栅栏以及如何使用它,我保证你管理原生资源的终结器都是buggy),对于正在寻找 C++ 风格的析构函数的人来说,这是一个有吸引力的麻烦。我说:告别终结者,并摆脱困境。
  • 您的问题可能不同,但答案是一样的。 (这就是为什么消息被表述为“这个问题在这里已经有了答案。”)如果没有有效的用例,那么弃用是很自然的。
  • @skomisa 太糟糕了,这个问题被关闭了。关于决策的基本原理,有一系列问题当然可以回答。参与决策的人可能会出现并回答它——这在以前肯定发生过,特别是关于最近的决定——或者答案可能会写在某个地方,要么在邮件列表中,要么在错误报告中的评论中.理由可能是主观的,但它可以是确定的,而不是纯粹基于意见。
  • @DanielPryden 你不知道答案是一样的,除非你参与了决定。我曾是;我不认为你是。

标签: java java-9 finalizer finalize finalization


【解决方案1】:

虽然问题是关于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 条。

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

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

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

  • 最终化很容易打破子类/超类关系

  • 终结者之间没有排序

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

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

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

以上是使用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 更精确并且描述了一个适度的演变我们对这个话题的思考。)

【讨论】:

  • 最终确定确实是一件复杂的事情。所以有一个小的修正:只有一个试图复活自己的终结器只能工作一次。但是终结器可能会复活一个不同的封装终结器可达对象,然后创建该包装器的新实例,该实例可以再次复活该对象,该过程可以执行任意次数。并不是说这是推荐的模式……
  • @Holger 是的。该规则是关于finalize 方法的调用,而不是关于“复活”本身。我已将文本更新为更准确。
  • @StuartMarks finalization can easily break subclass/superclass relationships 怎么样?
  • @GovindaSakare 简而言之,如果子类由于任何原因未能调用 super.finalize(),则不会发生超类的终结。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-01
  • 2018-01-01
  • 2017-09-24
相关资源
最近更新 更多