在Java CMS GC中避免升级失败

时间:2014-12-22 09:33:58

标签: java performance garbage-collection concurrent-mark-sweep

我有一个使用CMS垃圾收集的Java应用程序,它遭受了" ParNew(促销失败)"全天候GC几次(见下面的例子)。我知道当垃圾收集在旧一代中找不到足够的(连续的)空间来从新一代中提升对象时,会发生促销失败。在这一点上,它被迫做一个昂贵的世界末日GC。我想避免这样的事件。

我已阅读了几篇提出可行解决方案的文章,但我想在此澄清/巩固它们:

  1. -Xmx:增加堆大小,例如。从2G到4G - 简单的解决方案,为老一代提供更多的空间 - 似乎在我的经验中运作得相当不错
  2. -XX:NewRatio:增加NewRatio,例如。从2到4,为了增加老一代/减少新一代 - 给老一代更多的空间 - 到目前为止我的实验似乎没有太大的影响
  3. -XX:PromotedPadding:增加为避免促销失败而提供的填充量 - 但是我找不到有关为此参数赋予的值的任何建议 - 是否有人知道该值的含义,默认值是什么,或者要尝试什么价值?
  4. -XX:CMSInitiatingOccupancyFraction -XX:+ UseCMSInitiatingOccupancyOnly:让CMS循环更快启动以避免老一代的空间不足 - 我还没有尝试过这个解决方案 - 尝试哪些值是合理的?什么是默认值?
  5. 不要在堆上分配非常大的对象:一个非常大的对象可能难以推广,因为它需要在老一代中有大量连续的可用空间 - 这不适用于我的应用程序,就我而言我知道
  6. 如果它是相关的,这里是我当前的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 个答案:

答案 0 :(得分:7)

尽管增加内存确实是最简单和最通用的解决方案,但在这种情况下,似乎我们遇到了需要特定解决方案的特定问题。在我的案例中查看GC日志,我会看到这样的日志:

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

表明在CMS开始时旧版本已满92%(使用了3.1Gb中的2.9Gb)。所以JVM决定了#34;占用率"应该在90%左右。这是从默认开始的变化,我认为是68%左右。

显然我的应用程序的行为方式使JVM认为这是一件好事。但随后该应用程序似乎让JVM突然需要更多空间用来推广新一代的对象。

添加GC标志

-XX:CMSInitiatingOccupancyFraction=50 -XX:+UseCMSInitiatingOccupancyOnly

我们不再看到任何"促销失败"事件。这些标志分别将初始占用率设置为50%,并告诉JVM不要更改此分数。因此,只要旧gen达到50%以上,就会启动CMS。这可以避免它等到占用率提高到90%左右,其中促销失败的可能性很小。要高得多。

答案 1 :(得分:1)

增加内存是最简单的方法。仍然存在内存最终会被碎片化的风险(在极端情况下)我建议你使堆至少是完整GC后使用的内存大小的2.5倍。

CMS中的完整GC非常昂贵,因为它是一个串行集合而不是并行集合。

另一种方法是使用并行集合进行碎片整理,但不会回退到串行集合。

网络缓冲区和长字符串是较大的对象。如果他们真的很大,他们会直接进入任期空间,这些似乎是新空间中较大的物体,无法复制到终身空间。