分析和避免Java中的Full GC

4
我正在处理一个时间敏感的应用程序,每次迭代的时间限制为40毫秒。如果超过了这个时间限制,应用程序将被终止并游戏结束。应用程序本身没有问题,可以保持在40毫秒以下,但有时垃圾收集器会超过限制。
通过使用对象池和工厂模式相结合,我成功地减少了对垃圾收集的需求,并使应用程序在包括小型GC运行在内的稳定17毫秒迭代时间,除了在应用程序启动后的10到20次迭代之间,只发生一次单个完整的GC,这超过了40毫秒并导致我的应用程序崩溃。
我的问题是,如何分析是什么导致了这一次完整的GC?我已经积极使用jvisualvm来分析我的运行时和内存占用情况,并且它对识别需要缓存的对象非常有帮助。但在这种特殊情况下,我无法使用它,因为完整的GC发生在我按下jvisualvm中正确按钮之前很久。是否有一种方法可以以编程方式生成堆转储文件?

1
Java不是处理时间关键应用程序的好选择。然而,有一些特殊的虚拟机在这种情况下处理得更好。 - stefan bachert
3
抱歉,未能匹配语言和要求。这就像开车去月球一样。 - stefan bachert
2
如果您甚至无法更改虚拟机参数,那么如果您有任何专业责任,您应该告诉雇主/客户/邪恶外星霸主,这个问题是无法解决的。 - bmargulies
2
对于编程式堆转储生成,请在代码中抛出OutOfMemoryError,并给JVM提供-XX:-HeapDumpOnOutOfMemoryError选项。 - auselen
1
如果你想要良好的GC循环,你可能需要完全避免对象的创建和销毁。只需在程序开始时创建所需的可变对象,如果GC没有任何事情要做,那么你就可以放心了。你也可以尝试使用G1。 - ThePyroEagle
显示剩余7条评论
5个回答

3
我会使用商业内存分析工具YourKit。你可以从大多数这样的工具中获得免费的评估许可证,足以解决你的问题。 ;)
我发现VisualVM的一个问题是,在最小化对象创建时,最大的内存生产者是内存分析器本身。商业分析工具没有这个问题,因为它们使用堆外内存。
我建议你尽可能减少垃圾回收。17毫秒仍然很长,你可以考虑使用CPU分析。CPU分析器对于半毫秒以下的性能问题非常有用。如果低于此值,则需要使用自己的精确计时和一些试错。
我发现有用的东西是,在进行CPU分析和内存分析后,同时运行它们,你会得到更多关于如何改进的建议。
如果你减少了产生的垃圾量并增加了eden空间,你也许能够每天只进行一次小的GC。

http://vanillajava.blogspot.co.uk/2011/06/how-to-avoid-garbage-collection.html


1

你听说过HeapDumpOnCtrlBreak吗; 试试这个链接

或者可以使用

jmap -dump:format=b,file=<filename.hprof> <pid>

没有,我的键盘没有Break键。:( 但我可以在另一台电脑上尝试这个。 - Marcell
尝试使用Ctrl + /的替代方法。 - Bharat Sinha
使用jmap也很困难,因为我只有不到一秒的时间来查找pid并输入命令。最好在完整GC之前和之后以编程方式创建堆转储,以查看发生了什么变化。 - Marcell

1

JVM上的垃圾回收通常是一个巨大的优势,但主要缺点(正如您所指出的)是它有时会导致不可预测的GC暂停。

标准JVM适用于软实时(当您可以容忍偶尔的暂停),但不适合硬实时(即当任何超过给定容限的暂停都会导致错误/故障)

一些有用的建议包括:

  • 如果可以,请转向专门的实时JVM(例如Zing JVM)。这是可靠地获得Java实时行为的最佳方法
  • 使用低延迟库,例如Javolution - 这些库可以通过提供具有非常低的对象分配率的数据结构(因此减少GC开销)来帮助很多

1

您可以尝试jprofiler的评估版本,它允许您使应用程序等待分析器启动。它不会影响堆内存,并且通常是一个功能强大的分析器,具有良好的IDE集成和一键设置。

但是,由于您无法控制堆大小或GC设置,因此您将无法做太多事情。在启动时加载类会创建一些需要收集的垃圾。

我可以问一下:这是作业还是某种比赛/挑战?


是的,这是为了比赛。即使我无法更改gc设置,分析堆可以帮助我找出确切被垃圾回收的内容,并提供优化代码的提示。 - Marcell
1
@Marcel:你试过jprofiler吗?看起来它是你需要的工具。 - Denis Tulskiy
1
@Marcel:随便想一下,由于你有大约30毫秒的时间可以利用,你可以故意添加一些sleep()来让年轻代垃圾回收在将其发送到伊甸园或幸存区之前清理掉。 - Denis Tulskiy
我正在运行jprofiler. 它比jvisualvm更好,因为它可以直接从分析器中启动应用程序,并允许我在启动后瞥见堆。有很多ArrayList迭代器在那里消失了,不再出现(被垃圾回收?),所以我认为这就是问题所在。然而,我无法在堆行走器中看到它们,因此无法将它们追溯到它们来自的特定类。 - Marcell

0

你尝试过调整垃圾回收参数吗?如果你的机器有足够的RAM和多个核心,那么你应该能够利用并行垃圾回收。在Java 1.6 JVM上,默认情况下启用它(已链接)。基本上,如果你确保年轻代足够大,以便对象永远不会晋升到老年代(这是串行收集的),就可以了。


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