CMS收集器FullGC的原因

GC日志

2020-05-10T10:54:53.514+0800: 116965.198: [GC 116965.198: [ParNew (promotion failed): 1310720K->1310720K(1310720K), 9.4697020 secs]116974.668: [CMS2020-05-10T10:55:04.228+0800: 116975.911: [CMS-concurrent-mark: 5.113/24.345 secs] [Times: user=124.25 sys=9.90, real=24.34 secs] 
 (concurrent mode failure): 19185173K->5686761K(19890176K), 14.3322600 secs] 20230935K->5686761K(21200896K), [CMS Perm : 105711K->105711K(176268K)], 23.8028270 secs] [Times: user=34.33 sys=2.50, real=23.80 secs] 
Heap after GC invocations=81049 (full 25):
 par new generation   total 1310720K, used 0K [0x00000002c2000000, 0x0000000322000000, 0x0000000322000000)
  eden space 1048576K,   0% used [0x00000002c2000000, 0x00000002c2000000, 0x0000000302000000)
  from space 262144K,   0% used [0x0000000312000000, 0x0000000312000000, 0x0000000322000000)
  to   space 262144K,   0% used [0x0000000302000000, 0x0000000302000000, 0x0000000312000000)
 concurrent mark-sweep generation total 19890176K, used 5686761K [0x0000000322000000, 0x00000007e0000000, 0x00000007e0000000)
 concurrent-mark-sweep perm gen total 176268K, used 105711K [0x00000007e0000000, 0x00000007eac23000, 0x0000000800000000)
}

promotion failed

该问题是在进行Minor GC时,Survivor Space放不下,对象只能放入老年代,而此时老年代有碎片或者不能容纳这些对象造成的。(promotion failed时老年代CMS还没有机会进行回收,又放不下转移到老年代的对象,因此会出现下一个问题concurrent mode failure)。

concurrent mode failure

该问题是在执行CMS GC的过程中同时业务线程将对象放入老年代,而此时老年代空间不足,或者在做Minor GC的时候,新生代Survivor空间放不下,需要放入老年代,而老年代也放不下而产生的。

promotion failed和concurrent mode failure的触发区别

  • promotion failed是说,担保机制确定老年代是否有足够的空间容纳新来的对象,如果担保机制说有,但是真正分配的时候发现由于碎片导致找不到连续的空间而失败;
  • concurrent mode failure是指并发周期还没执行完,用户线程就来请求比预留空间更大的空间了,即后台线程的收集没有赶上应用线程的分配速度。

CMS并发周期失败的情况

  1. 并发模式失败(Concurrent mode failure):CMS的目标就是在回收老年代对象的时候不要停止全部应用线程,在并发周期执行期间,用户的线程依然在运行,如果这时候如果应用线程向老年代请求分配的空间超过预留的空间(担保失败),就回触发concurrent mode failure,然后CMS的并发周期就会被一次Full GC代替——停止全部应用进行垃圾收集,并进行空间压缩。如果我们设置了UseCMSInitiatingOccupancyOnlyCMSInitiatingOccupancyFraction参数,其中CMSInitiatingOccupancyFraction的值是70,那预留空间就是老年代的30%。
  2. 晋升失败:新生代做minor gc的时候,需要CMS的担保机制确认老年代是否有足够的空间容纳要晋升的对象,担保机制发现不够,则报concurrent mode failure,如果担保机制判断是够的,但是实际上由于碎片问题导致无法分配,就会报晋升失败。
  3. 永久代空间(或Java8的元空间)耗尽,默认情况下,CMS不会对永久代进行收集,一旦永久代空间耗尽,就回触发Full GC。

CMS的调优

1)promtiom failed 到 concurrent mode failure 的调优

一是survior区太小,二是调低CMSInitiatingOccupancyFraction的值

2)concurrent mode failure的调优

  • 增加整个堆的大小,增大老年代的空间,或者减少年轻代的大小
  • 以更高的频率执行后台的回收线程,即提高CMS并发周期发生的频率。设置UseCMSInitiatingOccupancyOnlyCMSInitiatingOccupancyFraction参数,调低CMSInitiatingOccupancyFraction的值,但是也不能调得太低,太低了会导致过多的无效的并发周期,会导致消耗CPU时间和更多的无效的停顿。
  • 增多回收线程的个数
    CMS默认的垃圾收集线程数是*(CPU个数 + 3)/4*,这个公式的含义是:当CPU个数大于4个的时候,垃圾回收后台线程至少占用25%的CPU资源。举个例子:如果CPU核数是1-4个,那么会有1个CPU用于垃圾收集,如果CPU核数是5-8个,那么久会有2个CPU用于垃圾收集。

2)针对永久代的调优

如果永久代需要垃圾回收(或元空间扩容),就会触发Full GC。默认情况下,CMS不会处理永久代中的垃圾,可以通过开启CMSPermGenSweepingEnabled配置来开启永久代中的垃圾回收,开启后会有一组后台线程针对永久代做收集,需要注意的是,触发永久代进行垃圾收集的指标跟触发老年代进行垃圾收集的指标是独立的,老年代的阈值可以通过CMSInitiatingPermOccupancyFraction参数设置,这个参数的默认值是80%。开启对永久代的垃圾收集只是其中的一步,还需要开启另一个参数——CMSClassUnloadingEnabled,使得在垃圾收集的时候可以卸载不用的类。

CMS的缺点

1.浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,自然会有新垃圾产生,这部分垃圾在标记过程之后,所以CMS无法在当收集中处理掉他们,只好留待下一次GC清理掉,这一部分垃圾称为浮动垃圾。如果CMS运行期间预留的内存无法满足程序的需要,就会出现"Concurrent Mode Failure",然后降级临时启用Serial Old收集器进行老年代的垃圾收集,这样停顿时间就很长了,所以-XX:CMSInitialOccupancyFraction设置太高容易导致大量"Concurrent Mode Failure"。

2.有空间碎片:CMS是一款基于“标记-清除”算法实现的,所以会产生空间碎片。为了解决这个问题,CMS提供了-XX:UseCMSCompactAtFullCollection开发参数用于开启内存碎片的合并整理,由于内存整理是无法并行的,所以停顿时间会变长。还有-XX:CMSFullGCBeforeCompaction,这个参数用于设置多少次不压缩Full GC后,跟着来一次带压缩的(默认为0)。

3.对CPU资源敏感,CMS默认启动的回收线程数是(cpu数量+3)/4,所以CPU数量少会导致用户程序执行速度降低较多。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章