12

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

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

分析应用程序显示大量的 Finalizer 对象,填充堆。好吧,我们认为“可能是奇怪的终结器线程减慢”问题,并审查了减少需要终结的对象数量(在这种情况下为 JNA 原生句柄)。无论如何都是个好主意,减少了数千个新对象......

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

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

FinalizerThread 仍然存在,询问 jConsole 他在等待。

编辑

首先,用 HeapAnalyzer 检查堆没有发现任何新的/奇怪的东西。HeapAnalyzer 有一些不错的功能,但一开始我遇到了困难。我正在使用 jProfiler,它带有很好的堆检查工具,并且会一直使用它。

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

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

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

4

6 回答 6

3

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

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

  • 我们有一个使用大量对象的高吞吐量服务器,这些对象最终实现了“最终确定”。这些对象主要是 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/中讨论(除了不太聪明的“不要使用终结器"..)。

感谢您的输入。

于 2012-05-07T10:57:09.903 回答
1

很难对您的困境给出具体的答案,但需要进行堆转储并通过 IBM 的 HeapAnalyzer 运行它。在以下位置搜索“堆分析器:http ://www.ibm.com/developerworks (直接链接不断变化)。如果您没有覆盖 finalize,那么终结器线程“突然停止工作”似乎极不可能。

于 2012-05-03T17:23:36.007 回答
1

终结器有可能被阻止,但我不知道它怎么会简单地死掉。

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

jConsole 他正在等待。

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

于 2012-05-03T17:25:21.050 回答
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); 
. . . 

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

于 2012-05-03T18:24:21.770 回答
0

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

于 2012-05-03T17:32:28.613 回答
0

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

于 2012-05-03T17:35:56.130 回答