CMS 收集器跟不上 Old Gen 的步伐

CMS collector not keeping pace with Old Gen

在中等繁忙的生产服务器上(50 个应用程序线程,30% CPU 利用率),我们看到 CMS 收集器跟不上提升到老年代的对象的情况。

我最初的想法是,这些对象显然仍被引用,因此不符合收集条件 - 但当 Old Gen 填充并提示串行收集时,6 GiB 中的 5.5 GiB 被回收。

Eden space 的大小为 3 GiB,大约需要 20-30 秒才能填满以启动新的收集。 Survivor space 使用量在 800 - 1250 MiB 之间波动,最大 1.5 GiB(每个)。

旧代中的对象符合收集条件,并且服务器拥有大量(明显的)资源,我不明白为什么 CMS 收集器没有保持在旧代大小之上:

是什么原因导致这种情况,有什么解决办法吗?

我知道占用率,但我不明白 CMSIncrementalSafetyFactor 的含义 - 我读过一些 Oracle 文档,但我不知道 "add[ing] conservatism when computing the duty cycle"实际上意味着..?

备选方案

切换到并行/吞吐量收集器会产生非常低的 GC 开销 (1.8%),但会偶尔(每天 50 次)长时间停顿 - 每次完整 GC 大约 20 秒。即使进行了一些调整,这也不太可能达到我们的最大暂停目标。

在理想情况下,我们可以使用 G1 收集器进行试验,但由于各种原因,我们只能使用 Java 6 JVM。

当您说 CMS 收集器跟不上您的对象提升速度时,这意味着您应该在 GC 日志中看到 "concurrent mode failures"。当 CMS 收集器 "loses the race" 而你 运行 在它完成之前内存不足时,你会得到这些。

2014-02-27T01:09:52.408-0600: 847.004: [GC 847.005: [ParNew 
(promotion failed)
Desired survivor size 78512128 bytes, new threshold 2 (max 15)
- age   1:   60284680 bytes,   60284680 total
- age   2:   32342648 bytes,   92627328 total
: 1380096K->1380096K(1380096K), 0.7375510 secs]847.743: 
[CMS2014-02-27T01:09:54.133-0600: 848.729: [CMS-concurrent-s
weep: 5.467/6.765 secs] [Times: user=21.59 sys=0.73, real=6.76 
secs]
  (concurrent mode failure): 2363866K->1763900K(4409856K),
10.6658960 secs] 3697627K->1763900K(5789952K), [CMS Perm : 
118666K->117980K(125596K)], 11.4061610 secs] 
[Times: user=11.34 sys=0.02, real=11.57 secs]

默认情况下,CMS 收集器将在老年代的占用率达到 92% 时触发。从你的老年代使用图表中的内存增长率来看,你每 5 分钟增长大约 500 MB。 6GB 的 92% 为您提供了大约 500 MB 的空间,这意味着 CMS 必须在不到 5 分钟的时间内赢得比赛,它会的。除非...

...除了我们在图表中看到的顺畅流量情况之外,您在幕后发生了一些事情。例如,您是否有任何后台进程来刷新内存中的数据结构(如缓存)?这些类型的活动会突然产生大量新的、长期存在的对象,这些对象需要提升到老年代。它会使您的平滑图形突然垂直,并且会很快耗尽可用内存。 CMS 收集器擅长处理流畅、稳定的流量,但它很容易受到activity 快速爆发的影响。它擅长响应垃圾生成率的逐渐变化,但它无法预测 "bursty" 行为,我见过很多这样导致它输掉比赛的案例。

除了完全避免产生突然爆发的新对象的后台进程之外,您可以通过将 CMSInitiatingOccupancyFraction 参数降低到 60-80 之间的某个值而不是默认值 92% 来让 CMS 收集器领先一步。

http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html#cms.starting_a_cycle

另外,也要注意你的 PermGen space。与并行吞吐量收集器不同,CMS 收集器默认情况下不收集 PermGen,因此如果它被填满,您最终会遇到一个 stop-the-world full GC。此参数使 CMS 收集器也收集 PermGen space:CMSClassUnloadingEnabled。

除此之外,我建议打开 GC 日志记录和设置: -XX:+PrintGCDetails 打印每个次要和主要垃圾收集的详细信息

这是一个很棒的参数,可让您在启动时查看每个 JVM 设置: -XX:+PrintFlagsFinal 在启动时打印所有 JVM 配置选项的值