Java 非常奇怪的OutOfMemoryError

Java 非常奇怪的OutOfMemoryError,java,out-of-memory,finalizer,Java,Out Of Memory,Finalizer,和往常一样,一个冗长的问题描述 我们目前正在对我们的产品进行压力测试,现在我们面临一个奇怪的问题。一到两个小时后,堆空间开始增长,应用程序在稍后的某个时间死亡 分析应用程序会显示大量终结器对象,填满堆。好吧,我们认为“可能是奇怪的终结器线程变慢”的问题,并回顾了减少需要终结的对象数量(本例中为JNA本机句柄)。好主意,减少了数千个新对象 接下来的测试显示了相同的模式,仅一小时后,并没有那么陡峭。这一次,终结器源于在测试床中大量使用的FileInput和FileOutput流。所有资源都已关闭,但

和往常一样,一个冗长的问题描述

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

分析应用程序会显示大量终结器对象,填满堆。好吧,我们认为“可能是奇怪的终结器线程变慢”的问题,并回顾了减少需要终结的对象数量(本例中为JNA本机句柄)。好主意,减少了数千个新对象

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

我不知道为什么在1到2个小时后(没有例外),FinalizerThread似乎突然停止工作。如果手动强制某些线程中的System.runFinalization(),探查器将显示终结器已清理。恢复测试会立即为终结器分配新的堆

FinalizerThread仍然在那里,询问jConsole他正在等待

编辑

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

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

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


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

很难对您的困境给出具体的答案,但请进行堆转储并通过IBM的HeapAnalyzer运行它。搜索“heap analyzer at:(direct link不断更改)。如果不覆盖finalize,那么终结器线程“突然停止工作”的可能性很小。

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

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

他在等你


要等待,它必须等待一个对象。

我的猜测:它是您自己的流(包装器)类中的一个覆盖关闭。由于流类通常是包装器,并委托给其他类,因此我可以想象这样一个嵌套的
新a(新B(新C()))
关闭时可能会导致一些错误的逻辑。您应该寻找两次关闭,委托关闭。可能还是忘记关闭(关闭错误的对象?).

对于缓慢增长的堆,当Java垃圾收集器在内存不足的情况下尝试延迟垃圾收集时,可能会耗尽内存。请尝试打开并发标记并使用清除垃圾收集,看看问题是否消失。

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


这意味着您的终结器可能正在等待流被释放。这意味着,正如Joop Eggen上面提到的,您的应用程序在关闭其中一个流时可能做了一些不好的事情。

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

最后一次测试已经进行了60多小时,没有任何问题。这使我们得出以下总结/结论:

  • 我们有一个高吞吐量的服务器,它使用了很多最终实现“finalize”的对象。这些对象主要是JNA内存句柄和文件流。构建finalizer的速度比GC和finalizer线程能够清理的速度快,这个过程在大约3小时后失败。这是一个众所周知的现象(->google)
  • 我们做了一些优化,使服务器摆脱了几乎所有的JNA终结器
  • 服务器比我们最初的尝试晚了几个小时死了
  • 探查器显示了大量终结器,这一次主要是由文件流引起的。即使在服务器暂停一段时间后,该队列也没有被清理
  • 仅在手动触发“System.runFinalization()”后,队列才被清空。正在恢复服务器时开始重新填充
  • 这仍然令人费解。我们现在猜测这是一些分析器与GC/终结的交互
  • 为了调试非活动终结器线程的原因,我们这次分离了探查器并附加了调试器
  • 系统运行时没有明显的缺陷…FinalizerThread和GC均为“绿色”
  • 我们重新开始了测试(现在是第一次,除了jConsole之外,没有附加任何代理),并且测试已经进行了60多个小时。因此,很明显,最初的JNA重构解决了这个问题,只是评测会话增加了一些不确定性(来自海森堡的问候)
例如,中讨论了管理终结器的其他策略(除了不太聪明的“不要使用终结器”)


感谢您的所有输入。

可能想看一看。一些想法:终结器确实不可靠。避免它们。@Charles我这样做了,谢谢。我认为(认为)我对GC细节已经足够流利了(直到现在)。在这里,我甚至没有实现“终结”“-我们谈论文件流…@Louis我担心有人会这样说:-)我会将其转发给oracle…这似乎很明显,但我可以向您保证,它不是ca
. . .
/*
 * 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); 
. . .