避免Java CMS GC中的晋升失败

7
我有一个使用CMS垃圾回收的Java应用程序,每天会发生几次"ParNew(promotion failed)"全GC(下面是示例)。我知道当垃圾回收无法在老年代中找到足够(连续)的空间来晋升新生代的对象时,将会发生晋升失败。此时,它被迫执行昂贵的停止全GC。我想避免这样的事件。
我已经阅读了几篇文章,提出了可能的解决方案,但我想在这里澄清/整合它们:
1. -Xmx:增加堆大小,例如从2G到4G--为老年代提供更多的空间--根据我的经验,这个简单的解决方案似乎效果不错。 2. -XX:NewRatio:增加NewRatio,例如从2到4,以增加老年代/减少新生代--为老年代提供更多的空间--从我的实验结果来看,似乎没有什么影响。 3. -XX:PromotedPadding:增加提供用于避免晋升失败的填充量--但我找不到任何关于该参数的建议值--是否有人知道该值的含义,默认值是什么或者应该尝试哪些值? 4. -XX:CMSInitiatingOccupancyFraction -XX:+ UseCMSInitiatingOccupancyOnly:使CMS周期更早地开始,以避免老年代空间不足--我尚未尝试此解决方案--应该尝试哪些值?默认值是什么? 5. 不要在堆上分配非常大的对象:非常大的对象可能难以晋升,因为它需要一个大的连续的老年代空间--据我所知,这不适用于我的应用程序。
如果相关的话,这里是我的当前GC选项和一个促销失败事件之前的日志样本。
-Xmx4g -XX:+UseConcMarkSweepGC -XX:NewRatio=1

2014-12-19T09:38:34.304+0100: [GC (Allocation Failure) [ParNew: 1887488K->209664K(1887488K), 0.0685828 secs] 3115998K->1551788K(3984640K), 0.0690028 secs] [Times: user=0.50 sys=0.02, real=0.07 secs] 
2014-12-19T09:38:35.962+0100: [GC (Allocation Failure) [ParNew: 1887488K->208840K(1887488K), 0.0827565 secs] 3229612K->1687030K(3984640K), 0.0831611 secs] [Times: user=0.39 sys=0.03, real=0.08 secs] 
2014-12-19T09:38:39.975+0100: [GC (Allocation Failure) [ParNew: 1886664K->114108K(1887488K), 0.0442130 secs] 3364854K->1592298K(3984640K), 0.0446680 secs] [Times: user=0.31 sys=0.00, real=0.05 secs] 
2014-12-19T09:38:44.818+0100: [GC (Allocation Failure) [ParNew: 1791932K->167245K(1887488K), 0.0588917 secs] 3270122K->1645435K(3984640K), 0.0593308 secs] [Times: user=0.57 sys=0.00, real=0.06 secs] 
2014-12-19T09:38:49.239+0100: [GC (Allocation Failure) [ParNew (promotion failed): 1845069K->1819715K(1887488K), 0.4417916 secs][CMS: 1499941K->647982K(2097152K), 2.4203021 secs] 3323259K->647982K(3984640K), [Metaspace: 137778K->137778K(1177600K)], 2.8626552 secs] [Times: user=3.46 sys=0.01, real=2.86 secs] 
2个回答

12

虽然增加内存确实是最简单和最普遍的解决方案,但在这种情况下,似乎我们需要一种特定的解决方案来解决特定的问题。查看我的GC日志,在我的情况下,我会看到像这样的日志:

GC (CMS Initial Mark) [1 CMS-initial-mark: 2905552K(3145728K)]

这表明在 CMS(Concurrent Mark and Sweep)开始时,旧代的使用率约为92%(使用了2.9GB,总共3.1GB)。因此,JVM决定“占用分数”应该约为90%。这与默认值不同,我认为默认值约为68%。

显然,我的应用程序以某种方式运行,使得JVM认为这是一件好事。但是,应用程序似乎让JVM吃惊了,因为它突然需要更多的旧代空间来提升新生代的对象。

添加GC标记后:

-XX:CMSInitiatingOccupancyFraction=50 -XX:+UseCMSInitiatingOccupancyOnly

我们再也没有看到任何“推广失败”事件。这些标志分别将初始占用率设置为50%,并告诉JVM不要更改此占用率。因此,一旦老年代超过50%,它将启动CMS。这避免了等待占用率上升至90%左右的情况,这时“推广失败”的机会更高。


1
增加内存是最简单的方法。仍有风险,即内存最终会被分段(在极端情况下)。我建议您将堆大小设置为完全GC后使用的内存大小的至少2.5倍。
CMS中的完全GC非常昂贵,因为它是串行收集而不是并行收集。
另一种选择是使用并行收集,它可以进行碎片整理,而不会回退到串行收集。
网络缓冲区和长字符串是较大的对象。如果它们确实很大,则会直接进入年老代空间,这些对象似乎是新生代中较大的对象,无法复制到年老代空间中。

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