为啥终结器会有“严重的性能损失”?

Posted

技术标签:

【中文标题】为啥终结器会有“严重的性能损失”?【英文标题】:Why do finalizers have a "severe performance penalty"?为什么终结器会有“严重的性能损失”? 【发布时间】:2011-02-21 01:17:05 【问题描述】:

有效的 Java 说:

使用终结器会导致严重的性能损失。

为什么使用终结器销毁对象会更慢?

【问题讨论】:

您可能会喜欢这篇文章,它讨论了终结器如何使对象再次可访问等。它还说明了为什么组合在相当多的情况下可以节省时间(而不是实现继承):java.sun.com/developer/technicalArticles/javase/finalization 【参考方案1】:

实际上遇到了这样一个问题:

在 Sun HotSpot JVM 中,终结器在被赋予固定低优先级的线程上处理。在高负载应用程序中,创建需要完成的对象比低优先级完成线程处理它们的速度更快。同时,最终挂起对象使用的堆上的空间不可用于其他用途。最终,您的应用程序可能会花费所有时间进行垃圾收集,因为所有可用内存都被等待终结的对象使用。

当然,这是不使用 Effective Java 中描述的终结器的其他许多原因之外的原因。

【讨论】:

【参考方案2】:

如果您仔细阅读 finalize() 的文档,您会注意到终结器使对象能够防止被 GC 收集。

如果不存在终结器,则可以简单地删除对象并且不需要更多关注。但如果有终结器,则需要在之后检查对象是否再次“可见”。

在不确切知道当前 Java 垃圾收集是如何实现的(实际上,因为那里有不同的 Java 实现,也有不同的 GC),你可以假设如果一个对象有一个 GC 必须做一些额外的工作终结器,因为这个特性。

【讨论】:

确实,这个页面还提到了 JVM 对具有非平凡终结器的对象的处理方式不同:fasterj.com/articles/finalizer2.shtml【参考方案3】:

我刚从办公桌上拿起一本《Effective Java》,想看看他指的是什么。

如果您阅读第 2 章第 6 节,他会详细介绍各种性能影响。

You can't know when the finalizer will run, or even if it will at all. Because those resources may never be claimed, you will have to run with fewer resources.

我建议阅读整个部分 - 它解释的东西比我在这里鹦鹉学舌要好得多。

【讨论】:

【参考方案4】:

因为垃圾收集器的工作方式。出于性能考虑,大多数 Java GC 使用复制收集器,其中短期对象被分配到“伊甸园”内存块中,当该代对象被收集时,GC 只需要复制那些仍然“活着”到更永久的存储空间,然后它可以一次擦除(释放)整个“伊甸园”内存块。这是高效的,因为大多数 Java 代码将创建数千个对象实例(盒装原语、临时数组等),其生命周期只有几秒钟。

但是,当您混合使用终结器时,GC 不能简单地一次擦除整个代。相反,它需要找出该代中需要终结的所有对象,并将它们排队在实际执行终结器的线程上。同时,GC 也无法有效地完成对对象的清理。因此,它要么必须使它们保持比应有的寿命更长,要么必须延迟收集其他对象,或两者兼而有之。此外,您还有实际执行终结器的任意等待时间。

所有这些因素加起来会导致显着的运行时损失,这就是为什么通常首选确定性终结(使用close() 方法或类似方法来显式终结对象状态)的原因。

【讨论】:

当然,这关注的是世代收藏家的问题。其他 GC 策略有不同的问题。但是它们都归结为 GC 需要执行额外的工作,包括至少两次遍历一个对象以释放它;一种将其添加到最终队列中,另一种用于在最终确定后实际释放它。 我认为几个常用的 Java API 类都有终结器来释放 O/S 资源是否正确?我在想FileOutputStream。所以某些对象的终结器不太可能会延迟不使用终结器的对象的 GC,因为大多数程序都会受到影响。 @Raedwald:对。例如,FileOutputStream 的 OpenJDK 实现有一个终结器,您可以通过查看 OpenJDK 源代码看到它。 (不过,我找不到任何要求标准库实现使用终结器的东西。)因此,在实践中,原本符合 GC 条件但仍在等待终结器的对象只是被提升为当终结器排队运行时,下一代(幸存者空间或终身)。但实际内存要等到下一次收集下一代老年代时才会回收。 假设一个对象同时具有close()finalize() 实现,如果我们显式调用close(),是否也会发生这种开销?【参考方案5】:

我能想到的一个原因是,如果您的资源都是 Java 对象而不是本机代码,那么显式内存清理是不必要的。

【讨论】:

【参考方案6】:

我的想法是这样的: Java 是一种垃圾回收语言,它根据自己的内部算法释放内存。每隔一段时间,GC 就会扫描堆,确定哪些对象不再被引用,然后释放内存。 终结器会中断此操作并强制在 GC 循环之外释放内存,从而可能导致效率低下。 我认为最佳实践是仅在绝对必要时使用终结器,例如释放文件句柄或关闭数据库连接,这应该确定地完成。

【讨论】:

它是真的强迫它,还是仅仅建议? 基本正确,但终结器不会在 GC 周期之外导致内存释放。相反,如果 GC 确定一个对象需要被终结,它会“复活”它并阻止对象被收集,直到终结器被执行。但这可能需要一段时间,因为 (IIRC) 终结器要等到下一次收集终身代时才会运行。 “我认为最佳实践是仅在绝对必要时使用终结器,例如释放文件句柄或关闭数据库连接”:请注意,这正是终结器合适的地方,因为终结器可能会任意延迟或根本不运行。

以上是关于为啥终结器会有“严重的性能损失”?的主要内容,如果未能解决你的问题,请参考以下文章

一文搞定,K8s集群可视化网络图,提升集群性能的利器!

为啥useSelector里面的选择器会运行两次?

为啥 Pycharm 的检查器会抱怨“d = ”?

生成了一个严重警告并将其发送到远程终结点。这会导致连接终止。TLS 协议所定义的严重错误代码是

为啥 DevkitARM GBA 链接器会检测到多个定义?

一套终结微服务!阿里大牛亲荐SpringCloud进阶手抄本限时开源~