避免在 Java CMS GC 中升级失败



我有一个使用CMS垃圾收集的Java应用程序,它每天都会遇到几次"ParNew(升级失败("的完整GC(请参阅下面的示例(。 我知道当垃圾回收在旧一代中找不到足够的(连续(空间来将新一代的对象提升到其中时,就会发生升级失败。 在这一点上,它被迫做一个昂贵的停止世界完整的GC。 我想避免此类事件。

我已经阅读了几篇建议可能的解决方案的文章,但我想在这里澄清/巩固它们:

  1. -Xmx:增加堆大小,例如从 2G 到 4G——在老一代中提供更多空间的简单解决方案——根据我的经验,似乎工作得很好
  2. -XX:NewRatio:增加NewRatio,例如从2增加到4,为了增加旧一代/减少新一代 - 给老一代更多的空间 - 到目前为止,我的实验似乎没有太大的影响,如果有的话
  3. -XX:提升填充:增加为避免提升失败而提供的填充量 - 但是我找不到任何关于为此参数提供什么值的建议 - 有谁知道该值的含义,默认值是什么,或者尝试什么值?
  4. -XX:CMS
  5. 占用率分数 -XX:+仅使用CMS占用率:使CMS周期尽早开始以避免旧一代空间不足 - 我还没有尝试过这个解决方案 - 尝试什么值是合理的? 默认值是什么?
  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] 

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

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

这表明旧一代在CMS开始时已满~92%(使用了3.1Gb中的2.9Gb(。 因此,JVM决定"占用率"应该在90%左右。 这与我认为大约 68% 的默认值相比发生了变化。

显然,我的应用程序的行为方式使JVM认为这是一件好事。 但是,该应用程序似乎让JVM感到惊讶,因为它突然需要在旧代中需要更多的空间来提升新一代的对象。

添加 GC 标志时

-XX:CMSInitiatingOccupancyFraction=50 -XX:+UseCMSInitiatingOccupancyOnly

我们不再看到任何"推广失败"事件。 这些标志分别将初始占用分数设置为 50%,并告诉 JVM 不要更改此分数。 因此,一旦老一代达到 50% 以上,它就会启动 CMS。 这样可以避免等到入住率达到90%左右,"晋升失败"的可能性要高得多。

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

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

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

网络缓冲区和长字符串是较大的对象。 如果它们真的很大,它们将直接进入保有权空间,这些似乎是新空间中较大的对象,无法复制到保有权空间。

最新更新