Java内存泄漏故障排除:最终确定?

2022-09-03 06:55:20

我有一个行为不端的应用程序,似乎泄漏了。经过简短的探查器调查后,大多数内存 (80%) 由实例持有。我怀疑终结器无法运行。java.lang.ref.Finalizer

造成这种情况的一个常见原因似乎是终结器引发的异常。但是,该类方法的javadoc(例如,请参阅此处)似乎自相矛盾:它声明finalizeObject

如果 finalize 方法引发未捕获的异常,则忽略该异常并终止该对象的终结。

但后来,它还指出

finalize 方法引发的任何异常都会导致此对象的终结停止,否则将被忽略。

我应该相信什么(即,最终确定是否停止?),你对如何调查这种明显的泄漏有什么建议吗?

谢谢


答案 1

两句话都说:

异常将导致此对象的终结被暂停/终止。

两句话还说:

未捕获的异常将被忽略(即 VM 不会以任何方式记录或处理)

所以这回答了你问题的前半部分。我对Finalers的了解还不够多,无法为您提供有关跟踪内存泄漏的建议。

编辑:我发现这个页面可能有用。它有一些建议,例如在终结器中手动将字段设置为 null,以允许 GC 回收它们。

编辑2:一些更有趣的链接和引用:

摘自《Java 终结器剖析》

终结器线程在系统上没有被赋予最大的优先级。如果“终结器”线程跟不上较高优先级线程导致可终结对象排队的速率,则终结器队列将继续增长并导致 Java 堆填满。最终,Java堆将耗尽,并且将抛出java.lang.OutOfMemoryError。

以及

它不能保证任何具有finize()方法的对象都被垃圾回收。

EDIT3:在阅读更多解剖学链接后,似乎在Finalizer线程中抛出异常确实会减慢速度,几乎与调用Thread.yield()一样多。您似乎是对的,即使引发异常,终结器线程最终也会将对象标记为能够进行 GC'd。但是,由于速度很慢,因此在您的情况下,Finalizer 线程可能无法跟上对象创建和超出范围的速率。


答案 2

我的第一步是确定这是否是真正的内存泄漏。

前面的答案中提出的要点都与收集对象的速度有关,而不是与是否收集对象的问题有关。只有后者才是真正的内存泄漏。

我们的项目也遇到了类似的困境,并以“慢动作”模式运行应用程序,以确定我们是否有真正的泄漏。我们能够通过减慢输入数据流来做到这一点。

如果在“慢动作”模式下运行时问题消失,则问题可能是前面答案中建议的问题之一,即Finalver线程无法足够快地处理终结器队列。

如果这就是问题所在,听起来你可能需要做一些重要的重构,如链接到的Bringer128页面所述,例如

现在,让我们看一下如何编写需要事后清理的类,以便其用户不会遇到前面概述的问题。最好的方法是将这些类分成两个 - 一个用于保存需要事后清理的数据,另一个用于保存其他所有内容 - 并仅在前者上定义终结器


推荐