【问题标题】:G1GC very high GC count and CPU, very frequency GCs that kill performanceG1GC 非常高的 GC 计数和 CPU,非常频繁的 GC 会影响性能
【发布时间】:2018-07-23 09:36:02
【问题描述】:

我最近将我的 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: ava 版本“1.8.0_144” Java(TM) SE 运行时环境 (build 1.8.0_144-b01) Java HotSpot(TM) 64 位服务器 VM(内部版本 25.144-b01,混合模式)

感谢您的帮助。

更新: 有关这些暂停的更多信息: 正如您所看到的,所有这些暂停都是 G1New,并且似乎它们与我的目标暂停时间一样长,即 30 毫秒。 当我在切换到 G1GC 之前查看 ParNew 暂停时,它看起来是这样的: 所以它们也都是年轻代集合(ParNew),但它们的频率较低且较短,因为它们仅在堆使用量达到 14GB 左右时才会发生(根据图表)

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

更新 2 我还注意到 NewRatio=2,我不知道 G1GC 是否尊重这一点,但这意味着我的 New Gen 的上限为 7GB。会不会是这个原因?

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

【问题讨论】:

  • 好的。将年轻代大小设置为 16 GB,看看是否有区别。您比我们更了解您的应用程序。
  • 看起来这个应用程序制造了很多容易处理的短命垃圾。 G1应该很合适。应该能够将 XX:InitiatingHeapOccupancyPercent 设置为相当高的 75 或 80,这应该使 GC 图看起来更像 CMS 图。
  • @JonahB 您在图像中看到的所有 GC 周期都是 G1New。如果我误解了 XX:InitiatingHeapOccupancyPercent 的含义,请纠正我。并发标记周期不是老一代集合的一部分吗? (在 G1 Collection Phases - Concurrent Marking Cycle Phases 部分查看oracle.com/technetwork/tutorials/tutorials-1876574.html)。如果是这样,提高该值将如何阻止 G1New 周期?
  • @ElliottFrisch,感谢您的回复。根据Oracle,如果我设置年轻代大小,G1GC 将不尊重目标暂停时间。 “通过 -Xmn 显式设置年轻代大小会干扰 G1 收集器的默认行为。G1 将不再尊重收集的暂停时间目标。因此,本质上,设置年轻代大小会禁用暂停时间目标。G1 不再是能够根据需要扩展和收缩年轻代空间。由于大小是固定的,所以不能改变大小"
  • 您可能还想尝试将 -Xms 设置为 低于 的值,而不是 -Xmx,从而为 VM 中的调整启发式算法提供一些发挥空间。

标签: java java-8 garbage-collection jvm g1gc


【解决方案1】:

我能够看到复制对象所花费的时间非常重要。看起来 G1GC 在对象提升为 Tenured Generation 之前默认有 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 我试图弄清楚粗化是否确实是一个问题。但它只是说设置 gc+remset=trace 我不明白如何在命令行中传递给 java,如果它甚至在 JDK 8 中可用。 为了以防万一,我稍微增加了 XX:G1RSetRegionEntries。

我希望它对未来的 G1GC 调谐器有所帮助,如果其他人有更多建议,那就太好了。

我仍然看到的是 [Processed Buffers] 在年轻的疏散中仍然需要很长时间,而 [Scan RS] 在混合收集中的时间很长。 不知道为什么

【讨论】:

    【解决方案2】:

    您的 GC 日志显示平均 2 秒的 GC 暂停间隔,每次大约 30-40 毫秒,这相当于应用程序吞吐量约为 95%。这并不等于“扼杀性能”领域。至少不是因为 GC 暂停。

    G1 虽然做更多的并发工作,例如对于记住的集合细化,您的暂停似乎在update/scan RS 中花费了一些时间,所以我假设并发 GC 线程也很忙,即它可能需要在 GC 暂停之外额外的 CPU 周期,默认情况下日志不包括,您为此需要+G1SummarizeRSetStats。如果延迟更重要,您可能希望为机器分配更多内核,如果吞吐量更重要,您可以调整 G1 以在暂停期间执行更多 RS 更新(以增加暂停时间为代价)。

    【讨论】:

    • 我添加了 +G1SummarizeRSetStats。我看到的是最近并发细化统计已处理 39371 卡 165 个完成的缓冲区:165 (100.0%) 由并发 RS 线程。 0 ( 0.0%) 由 mutator 线程。和 Rset 统计数据。我不明白为什么处理缓冲区需要这么多时间,默认情况下 -XX:G1RSetUpdatingPauseTimePercent=10 。如果我的目标 GC 暂停时间是 40 毫秒,那么更新 RSet 应该不会超过 0.1*40= 4 毫秒。
    • 评论不适合那些东西
    猜你喜欢
    • 2016-01-08
    • 1970-01-01
    • 2018-03-19
    • 2012-10-24
    • 2013-09-16
    • 2017-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多