为什么Java 9中废弃了finalize()方法?

31

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

是的,它可能会被错误地使用(例如仅一次保存对象免受垃圾收集或尝试在其中关闭一些本地资源,虽然这比不关闭好)。同样,许多其他方法也可能被错误地使用。

那么,finalize()是否真的如此危险或绝对无用,以至于必须将其从Java中删除?

(这个问题不同于为什么要实现finalize()?那个问题是关于Java平台的弃用,而另一个问题则是关于在应用程序中是否应该使用这种机制。)


4
请参考链接中与OpenJDK bug相关的讨论:https://bugs.openjdk.java.net/browse/JDK-8165641 - Daniel Pryden
3
简短的回答是:是的。与人们想象的相比,它更常无用,即使对于非常了解它的专家来说也很危险(例如,除非您知道可达性屏障是什么以及如何使用它,否则我保证管理本地资源的终结器都存在错误),并且对于寻找类似 C++ 析构函数的人来说,它是一个有吸引力的诱因。我认为:告别终结器是正确的选择。 - Daniel Pryden
2
你的问题可能不同,但是答案是相同的。这就是为什么消息被表述为“此问题已在此处有答案”的原因。如果没有有效的用例,那么弃用是很自然的。 - Daniel Pryden
4
很遗憾这个问题已经关闭了。有一些关于决策的理由 的问题可以被回答。参与决策的人可能会出现并回答它——这在以前确实发生过,特别是最近的决策——或者答案可能已经写在某个地方,无论是在邮件列表中的帖子或者缺陷报告中的注释。理由可能是主观的,但它可以是明确且不仅仅基于个人看法的。 - Stuart Marks
6
你不知道答案是否相同,除非你参与了决策。我参与了;我认为你没有参与。 - Stuart Marks
显示剩余10条评论
1个回答

45
尽管问题是关于Object.finalize方法的,但实际上涉及到整个finalization机制。这个机制不仅包括表面APIObject.finalize,还包括编程语言对对象生命周期的规定以及对JVM中垃圾回收器实现的实际影响。
已经有很多关于为什么从应用程序的角度使用finalization很困难的文章了。请参见为什么要实现finalize()方法?Java 9 Cleaner是否比finalization更好?及其答案。另请参见Joshua Bloch的《Effective Java, 3rd edition》第8项。
简而言之,使用finalizers存在以下问题:
  • 它们非常难以正确编程
  • 特别是当对象意外地变得不可达时(但是正确时),它们可能会意外运行;例如,请参阅我对这个问题的回答
  • 终结器很容易破坏子类/超类关系
  • 终结器之间没有顺序
  • JVM最多调用给定对象的finalize方法一次,即使该对象被“复活”
  • 没有关于终结时间的保证,甚至不能保证它会运行
  • 没有显式的注册或注销机制

以上是使用终结器的困难之处。考虑使用终结器的人应该重新考虑,鉴于上述问题列表。但是,这些问题是否足以废弃Java平台上的终结器?下面的部分解释了几个额外的原因。

终结可能使系统变得脆弱

即使您编写了正确使用终结的对象,当您的对象集成到更大的系统中时,它也可能会引起问题。即使您根本不使用终结,在集成到某些使用终结的更大系统的部分中,也可能会出现问题。一般的问题在于创建垃圾的工作线程需要与垃圾收集器保持平衡。如果垃圾收集器落后,至少有一些收集器可以“停止世界”并进行完整的收集以赶上。终结会使这种交互变得复杂。即使垃圾收集器跟上了应用程序线程,终结也可能引入瓶颈并减慢系统速度,或者它可能导致释放资源的延迟,从而耗尽这些资源。这是一个系统问题。即使实际使用终结的代码是正确的,在正确编程的系统中仍然可能出现问题。
(编辑2021-09-16:此问题描述了一个问题,即在低负载下系统运行良好,但在高负载下失败,可能是因为高负载下分配比终结的速率更快。)
终结对安全问题有影响。
Java的SEI CERT Oracle编码标准有一个规则MET12-J: 不要使用终结器。(注意,这是一个关于安全编码的站点。)特别是,它说:不正确使用终结器可能会导致垃圾回收就绪对象的复活,并导致拒绝服务漏洞。
Oracle的Java SE安全编码指南更明确地阐述了使用finalization可能出现的潜在安全问题。在这种情况下,使用finalization的代码本身并不是问题。相反,攻击者可以利用finalization攻击没有适当防御的敏感代码。特别是,指南7-3/OBJECT-3部分规定:

非final类的部分初始化实例可以通过finalizer攻击进行访问。攻击者在子类中覆盖受保护的finalize方法,并尝试创建该子类的新实例。此尝试失败......但攻击者只需忽略任何异常并等待虚拟机对部分初始化对象进行finalization。当发生这种情况时,恶意的finalize方法实现被调用,使攻击者可以访问this,即正在进行finalization的对象的引用。虽然对象只是部分初始化,但攻击者仍然可以调用它的方法......

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

Finalization增加了规范的复杂性

Java平台由多个规范定义,包括语言、虚拟机和类库API的规范。finalization的影响分散在所有这些规范中,但它反复表现出其存在。例如,finalization与对象创建有非常微妙的交互(已经足够复杂了)。finalization也已经出现在Java的公共API中,这意味着这些API的演变(直到现在)必须保持与先前指定的行为兼容。finalization的存在使得演化这些规范更加昂贵。
finalization增加了实现的复杂性。这主要涉及垃圾收集器。有几种垃圾收集实现,所有实现都需要支付实现finalization的成本。如果不使用finalization,则这些实现非常擅长最小化运行时开销。然而,实现仍然需要存在,并且需要正确和经过良好测试。这是一项持续的开发和维护负担。
我们已经看到,在程序员使用finalization方面并不推荐。然而,如果某些东西没有用处,那么不一定应该弃用它。上述观点说明了即使不使用finalization,机制在平台中的存在也会带来持续的规范、开发和维护成本。考虑到机制的无用性和它所带来的成本,弃用它是有意义的。最终,摆脱finalization将使每个人受益。
截至本文撰写时(2019年6月4日),Java没有具体计划删除finalization。然而,这确实是意图。我们已经弃用了Object.finalize方法,但尚未标记它以供删除。这是一个正式的建议,程序员停止使用此机制。非正式地已知不应使用finalization,但当然需要采取正式措施。此外,某些库类中的finalize方法(例如ZipFile.finalize)已被弃用“待删除”,这意味着这些类的finalization行为可能会在将来的版本中被删除。最终,我们希望在JVM中禁用finalization(可能首先是可选的,然后是默认的),并在将来的某个时间点从垃圾回收器中实际删除finalization实现。
(编辑2021年11月3日:JEP 421刚刚发布,提议弃用finalization以进行删除。在撰写本文时,它处于“候选”状态,但我预计它将前进。此JEP添加的弃用通知是正式通知,即finalization将在随后的Java版本中删除。也许不足为奇的是,这个答案和JEP中的材料有相当大的重叠,尽管JEP更加精确,并描述了我们在这个主题上的思考逐渐演变。)

(编辑于2022-04-04: JEP 421 弃用终结功能以便移除已经被整合并交付于JDK 18.)


2
@StuartMarks finalization可以很容易地破坏子类/超类关系 如何实现? - Govinda Sakhare
3
简而言之,如果一个子类由于任何原因未调用super.finalize(),则父类的finalization也不会发生。 - Stuart Marks
2
@barneypitt 嗯,你说的听起来“简单”,但实际上并不是这样。JVM需要仅在从第一个构造函数调用返回时启用finalization,而一般情况下,对象构造涉及许多嵌套的构造函数调用。可能可以通过一些努力来解决这个问题,但考虑到finalization本身就是“错误的编程模型”,我们决定放弃它。 - Stuart Marks
2
@barneypitt,你说得对,清理器也不应该是资源清理的首选方式。然而,它确实解决了许多终结问题。显式的选择(甚至选择退出)不仅确保不能在构造不足的对象上进行清理尝试,而且还可以在子类中注册自己的清理器,同时无法被子类覆盖。我不知道如何为finalize()方法修复这个问题。你只能允许重写或禁止重写。你不能有一个不可重写的操作并支持子类终结。 - Holger
1
@ceztko 不,我们没有。在C#/.NET中的Finalization与Java Finalization有很多相似之处,并且共享了几个基本缺陷:它可以“复活”不可达对象;需要另一个GC pass来回收Finalization后的对象;Finalization不能保证并且可能会在任意延迟后发生;没有对Finalizer的排序控制;阻塞的Finalizer可能会阻止其他Finalizer运行;除了其他问题外。请参见Dispose Pattern以获取更多讨论。 - Stuart Marks
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接