CMS是在JDK1.5后引入的一种以获取最短回收停顿时间为目标的垃圾收集器,它是基于"标记-清除"算法实现的。

CMS :全写Concurrent Mark Sweep,并发标记清除垃圾回收算法,是作用在JVM运行时数据区的老年代垃圾收集器。

开启办法:-XX:+UseConcMarkSweepGC

年轻代与CMS匹配使用的垃圾回收算法是:ParNew, 全写Parallel New

本文主要通过演示实例来探究CMS垃圾回收的过程以及其优缺点。

演示案例:

public class Test{
   
     public static void main(String []args) throws Exception{
        int i=0;
        while(true){
            Thread.sleep(3000);
            byte []alloc=new byte[++i*1024*1024]; 
        } 
     }   
}

JVM启动参数:

-XX:+UseConcMarkSweepGC  -Xms256M -Xmx256M -Xmn60M -XX:SurvivorRatio=10 -XX:+PrintGCDetails

启动后等待几秒,控制台会输入如下日志:

[GC (Allocation Failure) [ParNew: 27542K->918K(56320K), 0.0019112 secs] 27542K->918K(257024K), 0.0019725 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 28566K->918K(56320K), 0.0020238 secs] 28566K->918K(257024K), 0.0020726 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 29590K->765K(56320K), 0.0020224 secs] 29590K->765K(257024K), 0.0020720 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 30461K->918K(56320K), 0.0020770 secs] 30461K->918K(257024K), 0.0021241 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 31638K->918K(56320K), 0.0017705 secs] 31638K->918K(257024K), 0.0018185 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 32662K->918K(56320K), 0.0019661 secs] 32662K->918K(257024K), 0.0020149 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 33686K->765K(56320K), 0.0014997 secs] 33686K->765K(257024K), 0.0015912 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 34557K->0K(56320K), 0.0111977 secs] 34557K->683K(257024K), 0.0112437 secs] [Times: user=0.02 sys=0.01, real=0.01 secs] 
[GC (Allocation Failure) [ParNew: 34816K->0K(56320K), 0.0032008 secs] 35499K->683K(257024K), 0.0032587 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 35840K->0K(56320K), 0.0012557 secs] 36523K->683K(257024K), 0.0013206 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC (Allocation Failure) [ParNew: 36864K->0K(56320K), 0.0009824 secs] 37547K->683K(257024K), 0.0010308 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 37888K->0K(56320K), 0.0013021 secs] 38571K->683K(257024K), 0.0013683 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 38912K->0K(56320K), 0.0012634 secs] 39595K->683K(257024K), 0.0013100 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 39936K->0K(56320K), 0.0018402 secs] 40619K->683K(257024K), 0.0018991 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 40960K->0K(56320K), 0.0011309 secs] 41643K->683K(257024K), 0.0011772 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 41984K->0K(56320K), 0.0012026 secs] 42667K->683K(257024K), 0.0012497 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 43008K->0K(56320K), 0.0011927 secs] 43691K->683K(257024K), 0.0012412 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 44032K->0K(56320K), 0.0011553 secs] 44715K->683K(257024K), 0.0011989 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 45056K->0K(56320K), 0.0012037 secs] 45739K->683K(257024K), 0.0012504 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 46080K->0K(56320K), 0.0201685 secs] 46763K->683K(257024K), 0.0207550 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
[GC (Allocation Failure) [ParNew: 47104K->0K(56320K), 0.0012282 secs] 47787K->683K(257024K), 0.0012731 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 48128K->0K(56320K), 0.0012055 secs] 48811K->683K(257024K), 0.0012514 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [ParNew: 49152K->0K(56320K), 0.0013199 secs] 49835K->683K(257024K), 0.0014071 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 104107K(200704K)] 154283K(257024K), 0.0014047 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.009/0.009 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-abortable-preclean-start]
[GC (Allocation Failure) [ParNew: 50176K->0K(56320K), 0.0016528 secs][CMS[CMS-concurrent-abortable-preclean: 0.028/4.117 secs] [Times: user=0.05 sys=0.02, real=4.12 secs] 
 (concurrent mode failure): 157355K->616K(200704K), 0.0067083 secs] 207531K->616K(257024K), [Metaspace: 3233K->3233K(1056768K)], 0.0085474 secs] [Times: user=0.01 sys=0.01, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 110184K(200704K)] 110184K(257024K), 0.0002861 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.004/0.004 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 0 K (56320 K)][Rescan (parallel) , 0.0011192 secs][weak refs processing, 0.0001214 secs][class unloading, 0.0003481 secs][scrub symbol table, 0.0006184 secs][scrub string table, 0.0003710 secs][1 CMS-remark: 110184K(200704K)] 110184K(257024K), 0.0027154 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 114280K(200704K)] 114280K(257024K), 0.0001808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 0 K (56320 K)][Rescan (parallel) , 0.0002274 secs][weak refs processing, 0.0000622 secs][class unloading, 0.0002446 secs][scrub symbol table, 0.0003682 secs][scrub string table, 0.0001891 secs][1 CMS-remark: 114280K(200704K)] 114280K(257024K), 0.0011790 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 118376K(200704K)] 118376K(257024K), 0.0003026 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

其中下面这几个步骤是一次完整的CMS垃圾回收的过程:

[GC (CMS Initial Mark) [1 CMS-initial-mark: 114280K(200704K)] 114280K(257024K), 0.0001808 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 0 K (56320 K)][Rescan (parallel) , 0.0002274 secs][weak refs processing, 0.0000622 secs][class unloading, 0.0002446 secs][scrub symbol table, 0.0003682 secs][scrub string table, 0.0001891 secs][1 CMS-remark: 114280K(200704K)] 114280K(257024K), 0.0011790 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 

可以看出实际上CMS的执行过程是6个步骤(上图中红色字体),他们分别是:

1、CMS Initial Mark(初始标记):只标记与GC Roots【(1)、虚拟机栈中引用的对象;(2)、本地方法栈中JNI引用的对象;(3)、方法区中类的静态属性引用的对象;(4)、方法区中常量引用的对象】能直接关联到的对象,速度很快,该阶段需要STW

2、CMS-concurrent-mark(并发标记):对第一阶段标记过的对象进行GC Roots Tracing的过程,该阶段与应用线程一起并行执行

3、CMS-concurrent-preclean(预清理):查找上一阶段执行过程中从新生代晋升或新分配或被更新的对象,通过并发的扫描这些对象主要是减少下一阶段过程中STW的时间,该阶段与应用线程一直并行执行

4、CMS Final Remark(重新标记):修正并发标记过程中因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,该阶段停顿时间一般会比初始标记阶段稍长一些,但是远比并发标记所需的时间短,该阶段需要STW

5、CMS-concurrent-sweep(并发清除):对前几阶段标记的需要回收的对象进行并发清除,该阶段与应用线程一直并行执行

6、CMS-concurrent-reset(并发重置):重新初始化CMS内部数据结构,以备下一轮GC使用,该阶段与应用线程一直并行执行

优点:

1、并发收集

2、停顿时间低

缺点:

1、对CPU非常敏感:在并发标记阶段虽与应用线程一起执行,但是占用CPU资源会导致应用程序处理速度变慢,默认启动的 回收线程数量为(CPU数+3)/4, 也就是当CPU数>4时,并发回收时垃圾收集线程不少于25%的CPU资源,并且会随着CPU数量的增加而下降,当时当CPU数<4时,CMS对应用程序的影响就可能变得很大

2、无法处理浮动垃圾:在最后并发清理过程中,和用户线程并行执行,用户线程产生的新垃圾未被标记,只能等到下次gc的时候再进行清理,这部分垃圾就是浮动垃圾

3、使用的"标记-清除"算法会导致收集结束时产生大量的空间碎片:空间碎片过多时,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,导致提前触发一次Full GC

写在最后:很多面试官不动脑子就喜欢问这个过程,其实意义真的不大,照着网络上的各种总结背下来又能说明什么呢?或许面试官网上看到的就是4个步骤,难道就是所谓的标准答案了?所以实践出真知~,技术不是靠背下来的