调试Java内存泄漏:终结器?

16

我有一个表现异常的应用程序,看起来存在内存泄漏。经过简短的分析,大部分内存(80%)由 java.lang.ref.Finalizer 实例占用。我怀疑 finalizers 无法正常运行。

这种情况的常见原因是finalizer抛出异常。然而, Object 类的 finalize 方法的 javadoc(例如此处)似乎自相矛盾:它声明

  

如果finalize方法抛出未捕获的异常,则会忽略该异常并终止该对象的最终处理。

但稍后它还声明

  

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

我应该相信什么(即终结是否停止?),您对如何调查此类明显的内存泄漏有什么提示吗?

谢谢


1
我不会说这两个JavaDoc条目相互矛盾。第二个引用声明了:对于当前实例,终结被终止,但异常被忽略,因为其他对象仍可能被终结。 - Thomas
你看到这些实例持有哪些类型了吗?它们只是一组有限的类,还是遍布整个项目? - Joachim Sauer
终结者的秘密生活 http://www.fasterj.com/articles/finalizer1.shtml - leef
5个回答

9
两个引用都说:
异常将导致此对象的终结被停止/终止。
两个引用还说:
未捕获的异常被忽略(即不记录或以任何方式由VM处理)
这回答了你问题的前半部分。我对终结器不了解足够,无法为您提供有关查找内存泄漏的建议。
编辑:我发现this page可能会有用。它提供了一些建议,例如在终结器中手动将字段设置为null,以允许GC回收它们。
编辑2:一些更有趣的链接和引用:
来自Java Finalizer的解剖学 终结器线程在系统上没有被赋予最大优先级。如果“终结器”线程无法跟上更高优先级线程导致可终结对象排队的速度,终结器队列将继续增长并导致Java堆填满。最终,Java堆将耗尽并抛出java.lang.OutOfMemoryError。
而且,并不能保证具有finalize()方法的任何对象都会被垃圾回收。
编辑3:阅读更多解剖链接后,似乎在Finalizer线程中抛出异常会使其变慢,几乎与调用Thread.yield()一样。您似乎是正确的,即使抛出异常,终结器线程最终也会将对象标记为可以进行GC。然而,由于减速非常显著,因此可能在您的情况下,终结器线程无法跟上对象创建和超出范围的速度。

有趣。我本以为“停止”是指“停止”,而“终止...终止”则意味着“它继续进行,最终释放内存”。 - Rom1
对我来说,halted在这里的意思是停止。 - Manuel Selva
我更新了我的答案,包含一个看起来解释得非常好的链接。 - user545680

7
我的第一步将是确定这是否是一个真正的内存泄漏。之前回答中提到的问题都涉及对象收集的速度,而不是你的对象是否被完全收集。只有后者才是真正的内存泄漏。
我们在项目中遇到了类似的困境,并以“慢动作”模式运行应用程序,以确定我们是否有真正的泄漏。我们能够通过减缓输入数据流来做到这一点。
如果在“慢动作”模式下运行时问题消失,则问题可能是之前回答中建议的问题之一,即终结器线程无法快速处理终结器队列。
如果那是问题,听起来你可能需要进行一些非常规的重构,如Bringer128链接的page中所述。
现在让我们看一下如何编写需要事后清理的类,以便它们的用户不会遇到之前概述的问题。最好的方法是将这样的类分成两个部分--一个用于保存需要事后清理的数据,另一个用于保存其他所有内容,并仅在前者上定义终结器。

对这个想法点个赞。顺便问一下,你的问题是否和慢终结器有关? - user545680
1
那最初是我们怀疑的,但结果证明是其他问题(一个自制任务队列无法跟上高频数据)。我们有幸能够删除我们的终结器来测试我们的理论,而不会对系统行为产生致命影响。 - Phil Harvey

2
《Effective Java第二版》中的第7项建议是:“避免使用finalizers”。我强烈建议您阅读它。以下是一段摘录,可能会对您有所帮助:
“显式终止方法通常与try-finally结构结合使用,以确保终止。”

我已经阅读并同意。我不拥有我所工作的巨大代码库。 - Rom1
1
这并不直接回答你的问题,但我相信这是正确的方法,即使你必须修改另一个庞大的代码库。 - Manuel Selva
这回答了一个问题,即终结器是否适用于故障排除...它们并不适用。 - sjas

0

我和你有同样的问题(下图所示)。对于我们的情况来说,这是因为一个对象在其finalize方法中调用了wait(0),但却没有被通知到,从而阻塞了java.lang.ref.Finalizer$FinalizerThread。更多参考资料请见:

objects retained by Finalizer


0

我曾经遇到过类似的问题,即终结器线程无法跟上生成可终结对象的速度。

我的解决方案是使用MemoryMXBean.getObjectPendingFinalizationCount()来制作一个闭环控制,使用PD(比例和微分)控制算法来控制我们生成可终结对象的速度,因为我们只有一个入口来创建它,所以只需使用PD算法的结果睡眠数秒。虽然需要调整PD算法的参数,但效果很好。

希望这能帮到你。


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