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

J.J*_*eam 2 java finalize java-9

(此问题不同于您为什么要实现finalize()?此问题与从Java平台弃用有关,另一个问题与在应用程序中是否应使用此机制有关。)

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

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

那么,finalize()真的有这么危险的或绝对无用,它是必要踢出来的Java?

Stu*_*rks 7

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

从应用程序的角度,已经写了很多关于为什么难以使用终结处理的文章。看到问题,为什么要实施finalize()?并且Java 9 Cleaner是否应优先于定稿?和他们的答案。另请参见Joshua Bloch撰写的有效Java,第三版,条款8。

简要地说,有关使用终结器的问题的几点是:

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

  • 特别是当对象无法正常(但正确)无法到达时,它们可能会意外运行;例如,请参阅我对这个问题的回答

  • 最终确定可以轻松打破子类/超类关系

  • 终结器之间没有顺序

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

  • 无法保证最终确定的及时性,甚至根本无法保证

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

以上是使用finalization的困难。考虑到上述问题,正在考虑使用终结处理的任何人都应重新考虑。但是这些问题足以否决Java平台中的终结处理吗?以下各节中解释了几个其他原因。

最终确定可能会使系统易碎

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

最终确定会导致安全问题

适用于JavaSEI CERT Oracle编码标准具有一条规则MET12-J:不要使用终结器。(请注意,这是一个有关安全编码的网站。)特别是,它说

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

Oracle的Java SE安全编码指南对使用终结处理可能引起的潜在安全性问题更为明确。在这种情况下,使用终结处理的代码不是问题。取而代之的是,攻击者可以使用终结处理来攻击尚未适当防御的敏感代码。特别是,准则7-3 / OBJECT-3规定,

可以通过终结器攻击来访问非最终类的部分初始化实例。攻击者finalize在子类中重写受保护的方法,并尝试创建该子类的新实例。这种尝试失败了……但是攻击者只是忽略了任何异常,而是等待虚拟机对部分初始化的对象执行最终确定。发生这种情况时,将finalize调用恶意方法实现,从而使攻击者可以访问this,以引用最终确定的对象。尽管仅部分初始化了对象,但攻击者仍可以在其上调用方法。

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

最终确定增加了规格的复杂性

Java平台由几种规范定义,包括语言,虚拟机和类库API的规范。最终化的影响在所有这些方面都散布得很细,但它反复使人感觉到它的存在。例如,完成与对象创建之间的交互非常微妙(这已经足够复杂了)。Finalization也已经出现了Java的公共API,这意味着(到目前为止)需要对这些API进行改进以保持与先前指定的行为兼容。不断完善这些规范会使最终确定的成本更高。

最终确定给实施增加了复杂性

这主要是关于垃圾收集器的。有几种垃圾回收实现,所有这些都需要支付实现终结点的成本。如果不使用终结处理,则这些实现非常擅长于将运行时开销最小化。但是,实现仍然需要存在,并且需要正确且经过良好测试。这是持续的开发和维护负担。

摘要

我们在其他地方已经看到,不建议程序员使用终结处理。但是,如果某些内容没有用处,则不一定要弃用它。以上几点说明了一个事实,即即使不使用终结处理,平台中仅存在该机制也会增加规范,开发和维护成本。鉴于该机制缺乏实用性和所施加的成本,因此不建议使用该机制。最终,摆脱终结定论将使所有人受益。

截至撰写本文时(2019-06-04),尚无具体计划从Java中删除终结处理。但是,这样做当然是有意图的。我们已弃用该Object.finalize方法,但尚未将其标记为删除。这是程序员停止使用此机制的正式建议。非正式地知道不应该使用终结处理,但是当然有必要采取正式的步骤。另外,不赞成使用finalize库类中的某些方法(例如ZipFile.finalize)以“删除”,这意味着这些类的最终确定行为可以从将来的版本中删除。最终,我们希望在JVM中禁用终结处理(也许首先选择是可选的,然后在默认情况下再禁用),

  • @StuartMarks`终结很容易打破子类/超类关系`如何? (2认同)
  • @GovindaSakhare简要地说,如果子类由于任何原因未能调用super.finalize(),则不会发生超类的终结处理。 (2认同)
  • @barneypitt 嗯,你说的_听起来_很简单,但事实上并非如此。JVM 仅需要在从第一个构造函数调用返回时启用终结,而一般来说,对象构造涉及许多嵌套构造函数调用。也许可以通过一些努力来解决这个问题,但考虑到最终化从一开始就是_错误的编程模型_,我们决定放弃它。 (2认同)
  • @barneypitt,你是对的,清洁剂也不应该是资源清理的首选方式。然而,它确实解决了许多最终确定的问题。显式选择加入(甚至选择退出)不仅确保无法对构造不充分的对象进行清理尝试,而且子类仍然可以注册自己的清理器时,子类也无法覆盖它。我不明白如何解决“finalize()”方法的问题。您只能允许覆盖或禁止覆盖。您不能拥有不可重写的操作并支持子类终结 (2认同)