Java CMS 垃圾回收行为

6
我有一个应用程序会导致大量垃圾产生。首要条件是低 GC 暂停时间。我使用 visualgc 工具(和 gc 日志)尝试使用不同的 GC 参数。最佳参数如下。
-XX:+UseConcMarkSweepGC -Xmx1172M -Xms600M -XX:+UseParNewGC -XX:NewSize=150M
我的应用运行在 Java 1.6.0_21 的 SunOS 10 上。硬件为 2 x CPU 四核 (uname -X 结果为 numCPU = 8)。
问题如下:
观察 GC 行为,新对象创建在 eden 空间中,直到 eden 空间满了。当 eden 空间满了之后,GC 运行并清除垃圾,如果对象没有死亡,则将其复制到旧代。清除旧代空间时,一些 CMS 阶段是停止全局的 (暂停时间)。这是一个循环。
1.以上场景是否正确? 2.GC 清理老年代空间后,可能没有足够的空间扩展老年代空间吗?(XMS 和 XMS 值不同) 3.完整的 GC 操作是什么时候开始的?如何确定? 4.CMS-concurrent 阶段持续时间取决于 Eden 空间大小,实际上我的期望是,Eden 空间对 CMS-concurrent 阶段持续时间没有影响。在 CMS-concurrent 阶段中与 eden 空间有关的 GC 是什么? 5.还有什么建议可以帮助我最小化暂停时间?确实,对我来说最有价值的答案:)
谢谢
2个回答

10
你在使用 CMS 时不能忽略幸存者空间。CMS 不是紧凑收集器,这意味着如果你(或 JVM)将寿命阈值设置错误,那么对象将逐渐流入老年代中,导致老年代碎片增加并提前触发 CMS 强制执行,因为它没有足够的连续可用空间来处理从幸存者空间到老年代的晋升操作,这将强制进行全局垃圾回收,并且没有任何预警,因此需要一次完整的 STW 暂停。这需要多长时间取决于堆大小,但有一件事是非常可能的,就是它需要比正常 Eden 收集更长的时间。

还有其他几点需要注意:

  1. STW暂停不仅来自CMS,也来自young gen收集器
  2. CMS有2个STW阶段(标记和重新标记)和3-4个并发阶段,第一个STW阶段(标记)是严格单线程的,可能会引起问题(这里有一个讨论here
  3. 您可以控制处理并发阶段的线程数
  4. 您需要了解对象存活的时间,这可能意味着使用-XX:+PrintTenuringDistribution或者像您所做的那样用visualgc观察
  5. 然后,您可以使用-XX:SurvivorRatio来调整幸存者空间相对于eden的大小,以及-XX:MaxTenuringThreshold来控制对象在年轻收集之前可以存活多少次
  6. -XX:CMSInitiatingOccupancyFraction可用于指导CMS在启动CMS阶段之前需要填满多少(如果设置不当,将会出现严重暂停)

最终,您需要了解哪个收集器正在暂停,频率如何,持续时间如何,以及是否存在任何异常原因导致该暂停。然后,您需要将其与每个代的大小进行比较,以查看是否可以调整参数以尽量减少暂停的数量(和/或持续时间)。

请记住,由于需要长时间运行测试以查看其是否随时间恶化,因此这可能会成为时间陷阱。另外,如果没有可重复的自动化工作负载,几乎不可能得出任何关于您是否实际改进了事情的坚定结论。
一个好的内部摘要信息来源是Jon Masamitsu的博客。另一个关于此的好演示文稿是HotSpot Java VM中的GC调优

20小时后,gc日志大约记录了5次Full GC运行,我猜测运行Full GC的原因是"promotion failure"和"concurrent mode failure"。在谷歌上搜索这些原因。简而言之,对于"promotion failure",增加老年代大小,对于"concurrent mode failure",设置最小值为XX:CMSInitiatingOccupancyFraction。我将尝试将XX:CMSInitiatingOccupancyFraction设置为较小的值(如30或60),并增加堆大小。我会分享测试结果。 - Erdinç Taşkın
推广失败通常是我提到的碎片化问题,它迫使非并发全gc。您需要检查您的终身阈值并适当调整其大小。将初始占用设置为低值(默认值为70 iirc)只会意味着更频繁的全gc,并且没有太大作用,这不是好事。您是否有很多长寿命的东西?您可能会发现一个巨大的eden和一个微小的终身是一个不错的选择。 - Matt
低初始占用值更频繁地出现在CMS中,但没有问题。最大的问题是2-3秒的STW。吞吐量或0.0x秒的STW对我的情况不是问题。我已经尝试了较大的Eden大小,但STW持续时间增加了:(如何在并发阶段设置线程数? - Erdinç Taşkın
你能打印出那个暂停的GC日志输出吗?看看它花费时间做了什么会很有趣。使用ParallelCMSThreads来设置线程数,解释在这里 - Matt

2

最小化GC影响的最佳方法是尽量减少创建对象的数量。这并不总是易于实现或总体上最佳的解决方案,但它将最小化GC暂停时间。

如果无法减少对象数量,请尝试使它们的生命周期足够短,并且eden空间足够大,以便它们不会离开eden空间(或者使其非常长寿并且可重复使用)。

  1. 这里有三个需要关注的空间:eden -> survivor -> tenured。http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html

  2. GC试图确保在完整GC之后有足够的空闲空间,而-ms-mx选项控制它们的大小(前者称为-Xms-Xmx

  3. 完整GC在tenured空间满时开始,或者suvivor空间耗尽(例如从eden空间复制了太多对象),或者CMS决定现在是进行并发清理的好时间。

  4. CMS只清理tenured空间。

  5. 请参见我的先前答案。


我同意你关于增加eden空间的决定。我已经尝试了不同的newSize参数,并检查了gc日志中包含“Rescan”的行以暂停时间。较小的newSize值会导致较短的暂停时间。3个不同的newSize值与我的推断相平行。 - Erdinç Taşkın

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