如何处理Java中长时间的Full Garbage Collection循环周期

3
我们继承了一个运行在生产中并最近开始每10小时就崩溃的系统。基本上,如果系统无响应一分钟,我们的内部软件会标记该系统已经失败。我们发现我们的问题是Full GC循环持续1.5分钟,我们使用30 GB堆。现在的问题是,在短时间内我们不能进行很多优化,也不能快速地对服务进行分区,但我们需要尽快消除1.5分钟的停顿,因为这些停顿导致我们的系统在生产中崩溃。对于我们来说,可接受的延迟是20毫秒,但不再多。最快的调整系统的方法是什么?减少堆以频繁触发GC?使用System.gc()提示?还有其他解决方案吗?我们使用Java 8默认设置,并且我们有越来越多的用户 - 即越来越多的对象被创建。
一些GC统计: enter image description here

1
如果您的JVM支持它们,请尝试使用其他GC算法。 - Richard
1
全局垃圾回收(Full GC)是否真的成功地释放了大量空间? - dan.m was user2321368
3个回答

3
你的问题并没有一种万能解决方案:你需要对应用程序的分配和存活模式有很好的掌握,并且需要知道它与您运行的特定垃圾回收算法(Java版本及传递给java的命令行标志的函数)之间的相互作用。
广义上说,成功回收大量空间的Full GC意味着许多对象在小型集合中生存(但不会泄漏)。首先查看Eden和Survivor空间的大小:如果Eden太小,则小型集合将非常频繁地运行,也许您没有给对象一个机会在达到其tenuring阈值之前死亡。如果Survivors太小,对象将过早地晋升为老年代。
GC调整有点像艺术:您运行应用程序,研究结果,调整一些参数,然后再次运行。因此,您需要一个基准版本的应用程序,尽可能接近生产版本的行为,但希望不需要10个小时才能引起完全的GC。
由于你说你正在使用默认设置的Java 8,我认为这意味着你的Old collections正在使用Serial collector运行。通过切换到Old生成的并行收集器(-XX:+ UseParallelOldGC),您可能会看到一些非常快速的改进。虽然这可能会将1.5分钟的暂停减少到几秒钟(取决于您的盒子上的核心数量和为GC指定的线程数),但这不会将最大暂停时间减少到20毫秒。

1
@Mark - CMS可能会更快,也可能不会。 “最好的”GC高度依赖于您特定的应用程序和工作负载(以及在您的上下文中“最好”的含义)。唯一的方法是进行测试。 - dan.m was user2321368
1
谢谢,这是我们的GC日志 65154.265: [Full GC(人体工学)[PSYoungGen:2765438K->0K(8924160K)] [ParOldGen:24328402K->22508332K(24466944K)] 27093841K->22508332K(33391104K),[Metaspace:78790K->78163K(83968K)],80.8081759秒] [时间:用户=147.73 sys = 0.68,real = 80.81秒] 我认为我们已经在使用并行收集器 - Mark
1
@Mark - 文本“24 328 402K->22 508 332K”表示GC实际上没有成功收集老年代的大部分空间:老年代中已使用空间的大小从24吉字节减少到22吉字节。这意味着您可能真正耗尽了内存,或者存在某些泄漏。任何GC算法或调优都无法解决此问题! - dan.m was user2321368
1
@Mark - 这里有一个权衡,你分配的内存越多,GC 发生的时间就越长,但 GC 运行的时间也会更长。如果你有一个"重启窗口",并且能够给应用程序足够的内存使其到达该重启窗口,那么这可能非常有用。否则,你需要确定是否存在泄漏(并解决它),或者找出如何使用更少的内存。一个很好的第一工具是 "jmap" 实用程序——你可以使用它来转储堆上所有活动对象的直方图。 - dan.m was user2321368
1
请更新您的帖子,包括在评论中提到的其他细节。 - Juraj Martinka
显示剩余4条评论

3
您有很多保留的数据。有几个值得考虑的选项。
  • 将堆大小增加到32 GB,如果您有足够的空闲内存,则对性能影响较小。再次查看您的总计数,似乎您正在使用32 GB而不是30 GB,因此这可能没有帮助。
  • 如果您没有足够的空闲内存,可能会有一小部分堆被交换,这可能会大大增加完整GC时间。
  • 可能有一些简单的方法可以使数据结构更紧凑。例如使用紧凑字符串,使用基元代替包装器,例如用于时间戳的long而不是DateLocalDateTime。(long大小约为包装器的1/8)
  • 如果以上两种方法都不起作用,请尝试将一些数据移出堆。例如,Chronicle Map是一种并发映射,使用非堆内存可以大大减少GC时间。即存储在堆外的数据没有GC开销。添加此类功能的难易程度高度取决于数据结构的组织方式。
我建议分析您的数据结构,看看是否有任何简单的方法可以使其更有效率。

谢谢Peter,我不明白为什么堆增加对性能影响很小?你能提供详细的解释吗?因为这对我们来说是最快的解决方案,相比其他方法。 - Mark
1
@Mark,无论您使用30 GB还是32 GB堆,只要有空闲内存,它都不会减慢JVM的速度。相比之下,33 GB堆将会更慢,因为它必须使用64位引用。拥有更大的堆将减少GC之间的时间,并且更大的年轻代空间可以减少过早晋升的数量。 - Peter Lawrey
1
无论如何,您都应该考虑到,即使过去没有进行过这方面的优化,通过小的更改,可以显著减少内存使用量。您需要分析内存使用情况,例如使用飞行记录器来进行评估。大部分工作是调查,而不是对代码进行更改。但是,在许多情况下,您可以看到显著的减少。 - Peter Lawrey

0

当我遇到这种情况时,是由于静态变量导致的内存泄漏。我会检查所有最近的代码更改,并寻找任何可能的内存泄漏。


我们进行了分析,但没有泄漏。我们只是有了流量增长和更多的垃圾生成。 - Mark
也有可能是服务器配置问题,但我不知道你们的服务器是什么样子的。 - user2743227

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