【问题标题】:Why is finalize() only called once by garbage collector?为什么垃圾收集器只调用一次 finalize()?
【发布时间】:2015-10-27 05:39:26
【问题描述】:

引自 SCJP 6 学习指南:

finalize() 方法中,您可以编写传递引用的代码 将有问题的对象返回到另一个对象,从而有效地取消了该对象的垃圾收集资格。如果稍后同一对象再次符合垃圾收集条件,垃圾收集器仍然可以处理该对象并将其删除。然而,垃圾收集器会记住,对于这个对象,finalize() 已经运行,它不会再次运行 finalize()

为什么会这样设计?即使对象第二次被标记为收集,finalize() 方法的目的仍然有效。那么为什么 Java 决定跳过对 finalize() 的调用呢?

【问题讨论】:

  • 那句话出自哪里?
  • 因为垃圾回收假定 finalize() 不应该使对象不符合 GC 条件。如果是这样,这是一个错误,你应该修复它。无论如何,这很有趣,但在 18 年的 Java 编程中,我认为我从未使用过 finalize()。正确的代码不需要它。
  • @JBNizet 我也在想同样的事情,但是the Javadocs 没有指定那个合约,而是提到“finalize 方法可以采取任何行动,包括让这个对象再次对其他线程可用” (在这不是finalize 的常用用法的上下文中,但它仍然是一个选项)。
  • @Vulcan 嗯,很有趣。那么我会说,如果您使对象不合格,那可能是一个错误,如果不是,那么您就靠自己了,应该足够聪明,可以再次调用 finalize( ) 不必要的。我的主要观点是:在 99.9999% 的情况下,finalize() 是无用的。在您使用它的 99.9999% 的情况下,您不应该使对象不适合其中的 GC。
  • @JBNizet 你可能对“物体复活”这个词感兴趣。

标签: java garbage-collection finalize


【解决方案1】:

我不知道这是否是最初的原因,但当前的实现将 Finalizer 实例(Reference 的内部子类)排入队列,用于使用内部 ReferenceQueue 覆盖 finalize 方法的对象,该内部 ReferenceQueue 由专用 @ 轮询987654326@.

而且由于 JVM 无法知道对象是否需要第二次完成,所以它无法决定是否必须在调用 finalize() 方法后将新的 Finalizer 加入队列。

无论如何,您应该避免使用finalize()。它使对象分配的成本更高,阻止了逃逸分析,并且不是管理本机资源的一种非常可靠的方式,因为 GC 可以无限期地推迟最终确定。

【讨论】:

  • 第二。通常,从 GC 中“拯救”一个对象意味着将它放在某个地方以便再次使用它。如果那个“某个地方”必须是线程安全的,并且在 Java 中几乎所有东西都必须是线程安全的,那么从该列表中添加或删除对象的锁定成本可以与一开始的对象分配成本相当。如果你坚持在这个领域乱搞,除了教育原因之外,这是非常不建议的,至少做一个涉及线程的适当基准,而不是一个会撒谎的微基准。
【解决方案2】:

已启用终结器的对象不符合收集条件;然而,GC 仅在确定所有其他不符合收集条件的对象后检查它们,并记下所有 本应符合收集条件的对象但启用的对象的存在终结器,并尽快运行此类对象的finalize 方法。在终结器运行之前,可终结的对象不会有资格被收集,但是 GC 将无法区分一旦终结器完成就适合终结的对象,或者由于操作而被渲染为不适合终结的对象被某个对象的终结器处理,并在以后有资格被收集。

.NET Framework 包括称为 IIRC GC.SuppressFinalizeGC.ReRegisterForFinalization 的方法,这使得知道对象终结器的代码不会做任何有用的事情来告诉 GC 不要打扰调用它,并允许代码知道终结器“过早地”运行以请求稍后再次运行。但是,JVM 不包含这样的功能。由于在终结器运行后自动重新注册所有可终结对象以进行终结,这将阻止它们被收集,并且由于无法手动重新注册它们,因此最终结果是没有可用的模式来运行对象的终结器超过一次。

另一方面,可以通过定义一个可终结的嵌套类对象,让外部类对象持有对嵌套类实例的引用,并让该嵌套类实例的“终结”来实现类似的效果方法链返回到其所有者中的清理代码。如果该清理代码丢弃了嵌套类实例并将其替换为新实例,则该新实例将在下一个发现所有者未被引用的 GC 循环中触发其终结器(链接回其所有者)。

【讨论】:

  • 由于外对象和内对象都是相互引用的,实际上并不是外-内关系,而是一个循环。我记得曾经看到过相反的描述,一个可终结的外部对象通过包装到一个新的外部对象中来回收一个封装的内部对象……
  • @Holger:我建议嵌套类定义,这将在 之间建立明确的内部/外部关系。 instances 将保存循环引用(内部->外部引用是隐式的),但类型定义将是嵌套的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-22
  • 1970-01-01
  • 2023-03-07
  • 2019-05-23
相关资源
最近更新 更多