G1GC高GC计数和CPU占用过高,频繁的GC导致性能下降。

7

最近我将我的Java应用从CMS + ParNew切换到了G1GC。 当我进行切换时,我观察到CPU使用率上升了,GC计数和暂停时间也增加了。 在切换之前,我的JVM标志是

 java -Xmx22467m -Xms22467m -XX:NewSize=11233m -XX:+UseConcMarkSweepGC -XX:AutoBoxCacheMax=1048576 -jar my-application.jar

转换后我的标志是:

java -Xmx22467m -Xms22467m -XX:+G1GC -XX:AutoBoxCacheMax=1048576 -XX:MaxGCPauseMillis=30 -jar my-application.jar

我遵循了 Oracle 的最佳实践http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
Do not Set Young Generation Size

并没有设置年轻代的大小。 然而,我怀疑年轻代大小是问题所在。 我看到堆使用量在6-8 GB之间波动。 堆使用情况

相比之下,使用CMS和Par New时,内存使用量在4-16 GB之间增长,只有在这种情况下才会看到GC: 输入图像描述

我不确定为什么使用G1GC时GC如此频繁。当涉及到G1GC的GC调优时,我不确定自己漏掉了什么。

我正在使用Java 8: Java版本“1.8.0_144” Java(TM) SE Runtime Environment (build 1.8.0_144-b01) Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)

感谢您的帮助。

更新: 关于这些暂停的更多信息: 输入图像描述 你可以看到所有这些暂停都是G1New,而且它们似乎与我的目标暂停时间相同,即30毫秒。 当我看到在切换到G1GC之前的ParNew暂停时,情况是这样的: 输入图像描述 所以它们也都是年轻代集合(ParNew),但它们不太频繁且较短,因为它们仅在堆使用量达到约14GB时发生(根据图表)

我仍然不知道为什么G1New会这么早发生(就堆使用而言)

更新2: 我还注意到NewRatio=2,我不知道G1GC是否尊重它,但这意味着我的新生代被限制在7GB。这可能是原因吗?

更新3 添加G1GC GC日志: https://drive.google.com/file/d/1iWqZCbB-nU6k_0-AQdvb6vaBSYbkQcqn/view?usp=sharing


好的。将年轻代大小设置为16 GB,看看是否有所改变。您比我们更了解您的应用程序。 - Elliott Frisch
看起来这个应用程序产生了很多短暂的垃圾,很容易处理。G1 应该是一个很好的选择。应该能够将 XX:InitiatingHeapOccupancyPercent 设置得相当高-75 或 80,这应该使 GC 图表看起来更像 CMS 图表。 - Jonah Benton
2
@ElliottFrisch,感谢您的回复。根据Oracle的说法,如果我设置了年轻代大小,G1GC将不会尊重目标暂停时间。“通过-Xmn显式设置年轻代大小会干扰G1收集器的默认行为。G1将不再尊重收集的暂停时间目标。因此,实际上,设置年轻代大小会禁用暂停时间目标。 G1无法再根据需要扩展和收缩年轻代空间。由于大小是固定的,因此无法更改大小”。 - Michael P
是的,IHOP指导GC进行老年代垃圾回收。对于如此小的集合来说,图表中的暂停时间非常长。那里可能出了问题。如果您尝试设置较大的年轻代大小,则可能会出现与CMS相同的行为,但是-如果不知道应用程序-我猜测您将看到更长的暂停时间。如果在G1下看到许多但更短的集合,那将是增加年轻代的原因。IHOP旨在指导GC围绕垃圾生成速度相对于清理速度的快慢进行调整。增加应该允许GC将工作推向老年代,并延长集合之间的时间。 - Jonah Benton
1
你可能还想尝试将 -Xms 设置为比 -Xmx 的值,这样虚拟机中的调优启发式算法就有更多的空间来运行。 - Mark Rotteveel
显示剩余6条评论
2个回答

5
我发现复制对象所花费的时间非常重要。G1GC默认情况下有15个代,在对象晋升到老年代之前。我将其减少到1 (-XX:MaxTenuringThreshold=1)
此外,我不知道如何在日志中确认它,但是通过可视化GC日志,我发现年轻代不断地被调整大小,从最小值到最大值。我缩小了范围,这也提高了性能。
在这里查看https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector-tuning.htm#JSGCT-GUID-70E3F150-B68E-4787-BBF1-F91315AC9AB9,我试图弄清楚coarsenings是否确实是一个问题。但它只是建议设置gc+remset=trace,我不知道如何在命令行中传递它给Java,而且它是否在JDK 8中可用。我稍微增加了XX:G1RSetRegionEntries,以防万一。
我希望这对未来的G1GC调优者有所帮助,如果其他人有更多建议,那就太好了。
我仍然看到的是,在年轻代疏散中,[Processed Buffers]仍然需要很长时间,而在混合集合中,[Scan RS]非常长。不确定为什么。

2
您的GC日志显示平均GC暂停间隔为2秒,每个暂停大约30-40毫秒,这相当于应用程序吞吐量约为95%。这并不属于“杀死性能”的范畴。至少不是由于GC暂停引起的。
G1执行更多的并发工作,例如记忆集细化,您的暂停似乎花费了一些时间在update/scan RS,因此我假设并发GC线程也很忙,即可能需要在GC暂停之外额外的CPU周期,这在默认情况下不会被日志记录,您需要使用+G1SummarizeRSetStats。如果延迟更重要,您可能希望将更多的核心分配给机器,如果吞吐量更重要,您可以调整G1在暂停期间执行更多的RS更新(代价是增加暂停时间)。

1
我添加了+G1SummarizeRSetStats。我看到的是最近并发细化统计信息 已处理39371张卡片 在165个完成的缓冲区中: 由并发RS线程完成165个(100.0%)。 由mutator线程完成0个(0.0%)。 以及Rset统计信息。我不明白的是为什么处理缓冲区需要这么长时间,当-XX:G1RSetUpdatingPauseTimePercent = 10默认时。 如果我的目标GC暂停时间为40ms,则更新RSet所需的时间不应超过0.1 * 40 = 4ms。 - Michael P
注释不适合那种东西。 - the8472

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