非常奇怪的OutOfMemoryError

时间:2012-05-03 17:08:25

标签: java out-of-memory finalizer

一如既往,一个冗长的问题描述。

我们目前正在对我们的产品进行压力测试 - 现在我们面临一个奇怪的问题。一到两个小时后,堆空间开始增长,应用程序稍后会死。

对应用程序进行概要分析会显示大量的Finalizer对象,从而填充堆。好吧,我们认为“可能是一个奇怪的终结器线程减慢”问题,并审查了减少需要最终确定的对象的数量(在这种情况下为JNA本机句柄)。无论如何都是好主意,减少了成千上万的新物......

接下来的测试显示了相同的模式,仅在一小时后并没有那么陡峭。这次Finalizers起源于在测试平台中大量使用的FileInput-和FileOutput流。所有资源都已关闭,但终结器不再清理。

我不知道为什么在1或2小时后(没有例外),FinalizerThread似乎突然停止工作。如果我们在一些线程中手动强制执行System.runFinalization(),则分析器会显示清除终结器。立即恢复测试会导致终结器的新堆分配。

FinalizerThread仍在那里,询问jConsole他是在等待。

修改

首先,使用HeapAnalyzer检查堆没有发现任何新的/奇怪的。 HeapAnalyzer有一些不错的功能,但我起初遇到了困难。我使用jProfiler,它附带了很好的堆检测工具,并将继续使用它。

也许我错过了HeapAnalyzer中的一些杀手级功能?

其次,今天我们使用调试连接而不是分析器来设置测试 - 系统现在稳定了将近5个小时。这似乎是一个非常奇怪的组合,包括太多的终结器(在第一次审查中已经减少),分析器和VM GC策略。由于目前一切运行良好,没有真正的见解......

感谢您到目前为止的输入 - 也许您保持关注和感兴趣(现在您可能有更多理由相信我们不会谈论简单的编程错误)。

6 个答案:

答案 0 :(得分:3)

我想用当前状态的摘要来结束这个问题。

最后一次测试现在超过60小时没有任何问题。这导致我们得出以下总结/结论:

  • 我们有一个使用大量对象的高吞吐量服务器,最终实现“finalize”。这些对象主要是JNA内存句柄和文件流。比GC和终结器线程更快地构建终结器能够清理,此过程在约3小时后失败。这是一个众所周知的现象( - > google)。
  • 我们做了一些优化,所以服务器摆脱了几乎所有的JNA终结器。此版本已使用附带的jProfiler进行测试。
  • 服务器比我们最初的尝试晚了几个小时......
  • 分析器显示了大量的终结器,这次主要仅由文件流引起。即使在暂停服务器一段时间后,也没有清除此队列。
  • 仅在手动触发“System.runFinalization()”后,队列才会清空。恢复服务器开始重新填充......
  • 这仍然是莫名其妙的。我们现在猜测这是与GC / finalization的一些分析器交互。
  • 为了调试可能是非活动终结器线程的原因,我们分离了探查器并且这次附加了调试器。
  • 系统运行时没有明显的缺陷...... FinalizerThread和GC都是“绿色”。
  • 我们恢复了测试(现在是第一次没有任何代理,除了jConsole附加),现在已经超过60小时了。显然,最初的JNA重构解决了这个问题,只有剖析会增加了一些不确定性(Heisenberg的问候)。

管理终结器的其他策略例如在http://cleversoft.wordpress.com/2011/05/14/out-of-memory-exception-from-finalizer-object-overflow/中讨论(除了不过分聪明的“不使用终结器”......)。

感谢您的所有输入。

答案 1 :(得分:1)

难以针对您的困境给出具体答案,但需要通过IBM的HeapAnalyzer进行堆转储并运行它。在http://www.ibm.com/developerworks搜索"堆分析器(直接链接不断变化)。似乎终结者线程突然停止工作"如果你没有最终确定。

答案 2 :(得分:1)

终结器有可能被阻止,但我不知道它是如何死的。

如果你有很多FileInputStream和FileOutputStream finalize()方法,这表明你没有正确关闭你的文件。确保这些流始终在finally块中关闭或使用Java 7的ARM。 (自动资源管理)

  

jConsole他正在等待。

要等待它必须等待一个物体。

答案 3 :(得分:1)

FileInputStream和FileOutputStream的finalize()方法都有相同的注释:

. . .
/*
 * Finalizer should not release the FileDescriptor if another
 * stream is still using it. If the user directly invokes
 * close() then the FileDescriptor is also released.
 */
     runningFinalize.set(Boolean.TRUE); 
. . . 

这意味着您的Finalizer可能正在等待流发布。这意味着,正如上面提到的Joop Eggen所说,当关闭其中一个流时,你的应用可能会做坏事。

答案 4 :(得分:0)

我的猜测:它是你自己的流(包装器)类中的覆盖关闭。由于流类通常是包装器并且委托给其他人,我可以想象这样一个嵌套的new A(new B(new C()))可能会在关闭时导致一些错误的逻辑。你应该寻找两次关闭,委托结束。也许还有一些被遗忘的关闭(靠近错误的物体?)。

答案 5 :(得分:0)

随着堆缓慢增长,当Java垃圾收集器尝试在低内存情况下进行垃圾收集时,它可能会耗尽内存。尝试使用-XX:+UseConcMarkSweepGC启用并发标记和清除垃圾回收,并查看问题是否消失。