【JVM】CMS垃圾回收器

一、简介

Concurrent Mark Sweep,是一种以获取最短回收停顿时间为目标的收集器,尤其重视服务的响应速度。

CMS是老年代垃圾回收器,基于标记-清除算法实现。新生代默认使用ParNew收集器,基于复制算法

二、垃圾回收过程

分为四个步骤进行垃圾回收:初始标记,并发标记,重新标记,并发清除。只有初始标记和重新标记需要停顿。

  • 初始标记。只是标记一下GC Roots能直接关联到的老年代对象,速度很快。这一阶段会STW
  • 并发标记。就是进行GC Roots的Tracing,处理器可以与用户线程一起工作。
  • 重新标记。为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段也会触发STW,停顿时间会比初始标记阶段稍长,远比并发时间短。
  • 并发清除,处理器可以与用户线程一起工作。

可达性分析方法

  • 通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连时,说明这个对象是可回收的。
  • Java语言中,可作为GC Roots的对象包括以下几种:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中引用的对象。

三、垃圾回收触发时机

新生代垃圾回收触发时机:当 eden 区内存无法为一个新对象分配内存时,就会触发 Minor GC

老年代垃圾回收触发时机:

1、如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,虚拟机会根据收集的数据决定是否触发(建议线上环境带上这个参数,不然会加大问题排查的难度)。

2、老年代使用率达到阈值
CMSInitiatingOccupancyFraction,默认92%。

3、永久代的使用率达到阈值
CMSInitiatingPermOccupancyFraction,默认92%,前提是开启
CMSClassUnloadingEnabled

4、新生代的晋升担保失败。老年代没有足够的空间来容纳全部的新生代对象或历史平均晋升到老年代的对象,如果不够的话,就提早进行一次老年代的回收,防止下次进行YGC的时候发生晋升失败。

四、垃圾回收如何做到STW

JVM有个叫做“安全点”和“安全区域”的东西,在发生GC时,所有的线程都会执行到“安全点”停下来。
在需要GC的时候,JVM会设置一个标志,当线程执行到安全点的时候会轮询检测这个标志,如果发现需要GC,则线程会自己挂起,直到GC结束才恢复运行。

但是对于一些没有获得或无法获得CPU时间的线程,就没办法等到它执行到安全点了,所以这个时候只要这个线程是在安全区域的,也可以进行GC,安全区域是一段代码段,在这段代码段中对象的引用关系不会发生变化,所以这个时候进行GC也是安全的。

五、关于FullGC

  • Full GC == Major GC指的是对老年代/永久代的stop the world的GC
  • Full GC的次数 = 老年代GC时 stop the world的次数
  • Full GC的时间 = 老年代GC时 stop the world的总时间
  • CMS 不等于Full GC,我们可以看到CMS分为多个阶段,只有stop the world的阶段被计算到了Full GC的次数和时间,而和业务线程并发的GC的次数和时间则不被认为是Full GC。CMS主要可以分为initial mark(stop the world), concurrent mark, remark(stop the world), concurrent sweep几个阶段,其中initial mark和remark会stop the world

 

六、CMS缺点

  • 垃圾回收时会占用一部分线程,导致系统变慢,总吞吐量会降低。
  • 无法处理浮动垃圾,需要预留足够的内存空间给用户线程使用,可以通过 -XX:CMSInitiatingOccupancyFraction 参数控制触发垃圾回收的阈值。
  • 如果预留的内存无法满足程序需要,就会出现“Concurrent Mode Failure”失败,这时将启动应急预案,启用Serial Old 进行垃圾回收,停顿时间会变长。所以-XX:CMSInitiatingOccupancyFraction 参数的值设置的太高,会导致频繁“Concurrent Mode Failure”失败,性能反而降低。
  • 标记-清理,容易产生内存碎片。-XX:+UseCMSCompactAtFullColletion 开启碎片整理功能,默认开启,-XX:CMSFullGCsBeforeCompaction,控制多少次不压缩的FullGC之后来一次带压缩的

七、常见问题

1、promotion failed – concurrent mode failure

Minor GC后, Survivor空间容纳不了剩余对象,将要放入老年代,老年代有碎片或者不能容纳这些对象,就产生了concurrent mode failure, 然后进行stop-the-world的Serial Old收集器。

解决办法:-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5 或者调大新生代或者Survivor空间

2、concurrent mode failure

CMS是和业务线程并发运行的,在执行CMS的过程中有业务对象需要在老年代直接分配,例如大对象,但是老年代没有足够的空间来分配,所以导致concurrent mode failure, 然后需要进行stop-the-world的Serial Old收集器。

解决办法:+XX:CMSInitiatingOccupancyFraction,调大老年带的空间,+XX:CMSMaxAbortablePrecleanTime

3、碎片整理

-XX:+UseCMSCompactAtFullCollection  强制进行空间碎片整理
CMS 采用标记算法,会产生大量的空间碎片。以上参数就是强制执行一次空间碎片整理,但是空间碎片整理会引发STW。

-XX:+CMSFullGCsBeforeCompaction 配置经过几次的FullGC进行空间碎片整理
-XX:+CMSFullGCsBeforeCompaction=10  经过10次FGC后进行空间碎片整理,以降低STW次数

 

点赞