Java垃圾收集器之CMS收集器

1、特点

CMS收集器是JAVA虚拟机中垃圾收集器的一种。它运行在JAVA虚拟机的老年代中。CMS是(Concurrent MarkSweep)的首字母缩写。CMS收集器是一种以获取最短回收停顿时间为目标的收集器。比较适用于互联网等场合,可能是互联网中最重要的收集器模式;

2、优点

由于整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。因此CMS是一款优秀的收集器,具备了并发收集、低停顿的优点,Sun的一些官方文档里面也称之为并发低停顿收集器(Concurrent Low Pause Collector)。

3、缺点

CMS收集器对CPU资源非常敏感

面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程数是(CPU数量+3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集线程最多占用不超过25%的CPU资源。但是当CPU不足4个时(譬如2个),那么CMS对用户程序的影响就可能变得很大,如果CPU负载本来就比较大的时候,还分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,这也很让人受不了。为了解决这种情况,虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep / i-CMS)的CMS收集器变种,所做的事情和单CPU年代PC机操作系统使用抢占式来模拟多任务机制的思想一样,就是在并发标记和并发清理的时候让GC线程、用户线程交替运行,尽量减少GC线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得少一些,速度下降也就没有那么明显,但是目前版本中,i-CMS已经被声明为“deprecated”,即不再提倡用户使用。

CMS收集器无法处理浮动垃圾

由于CMS并发清理阶段用户线程还在运行着,伴随程序的运行自然还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理掉它们,只好留待下一次GC时再将其清理掉。这一部分垃圾就称为“浮动垃圾(Floating Garbage)”。也是由于在垃圾收集阶段用户线程还需要运行,即还需要预留足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以便降低内存回收次数以获取更好的性能。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高将会很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。

收集结束时会产生大量空间碎片

CMS是一款基于“标记-清除”算法实现的收集器,在收集结束时会产生大量空间碎片。空间碎片过多时,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数,用于在“享受”完Full GC服务之后额外免费附送一个碎片整理过程,内存整理的过程是无法并发的。空间碎片问题没有了,但停顿时间不得不变长了。虚拟机设计者们还提供了另外一个参数-XX: CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。

 

4、代码

package com.gc;

 

import java.util.ArrayList;

import java.util.List;

 

/**

 * 简单的JAVA虚拟机内存回收,cms收集器的使用

 * 参数:-Xms30m -Xmx60m-Xmn10m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails

 * @author 范芳铭

 */

public class EasyCMS {

      public byte[] placeHolder =new byte[64 * 1024]; //占位符

      public static voidmain(String[] args) throws Exception{

              outOfMemoryByExpansionSize();

      }

     

     

      private static voidoutOfMemoryByExpansionSize() throws Exception{

              Listlist = new ArrayList();

              while(true){

                    EasyCMS serial =new EasyCMS();

                    list.add(serial);

                    Thread.sleep(10);//停顿10毫秒

              }

      }

}

5、参数

参数:-Xms30m -Xmx60m-Xmn10m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails

-Xms30m  JVM最小30M

-Xmx100m  JVM最大100M

-Xmn10m  新生代固定10M

-XX:+ UseConcMarkSweepGC对应parNew + cms 收集器

-XX:+PrintGCDetails 打印详细GC

6、运行结果

[GC [ParNew: 9110K->972K(9216K),0.0056278 secs] 48708K->48702K(60416K), 0.0056664 secs] [Times: user=0.00sys=0.00, real=0.01 secs]

[GC [1 CMS-initial-mark:47730K(51200K)] 48766K(60416K), 0.0000829 secs] [Times: user=0.00 sys=0.00,real=0.00 secs]

[CMS-concurrent-mark: 0.003/0.003secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[CMS-concurrent-preclean:0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[GC[YG occupancy: 1036 K (9216K)][Rescan (parallel) , 0.0001092 secs][weak refs processing, 0.0000140 secs][1 CMS-remark: 47730K(51200K)] 48766K(60416K), 0.0001609 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]

[CMS-concurrent-sweep: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[CMS-concurrent-reset: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[GC [ParNew: 9112K->9112K(9216K),0.0000185 secs][CMS: 47728K->51175K(51200K), 0.0184446 secs]56840K->56814K(60416K), [CMS Perm : 2086K->2085K(12288K)], 0.0185255secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

[GC [1 CMS-initial-mark:51175K(51200K)] 56878K(60416K), 0.0000866 secs] [Times: user=0.00 sys=0.00,real=0.00 secs]

[CMS-concurrent-mark: 0.003/0.003secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[CMS-concurrent-preclean:0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[CMS-concurrent-abortable-preclean:0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[GC[YG occupancy: 5703 K (9216K)][Rescan (parallel) , 0.0001108 secs][weak refs processing, 0.0000041 secs][1 CMS-remark: 51175K(51200K)] 56878K(60416K), 0.0001523 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]

[CMS-concurrent-sweep: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[CMS-concurrent-reset: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

[GC [ParNew: 9103K->9103K(9216K),0.0000140 secs][CMS: 51172K->51172K(51200K), 0.0135952 secs]60275K->60269K(60416K), [CMS Perm : 2085K->2084K(12288K)], 0.0136662secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

[Full GC [CMS:51172K->51172K(51200K), 0.0041339 secs] 60269K->60269K(60416K), [CMS Perm: 2084K->2084K(12288K)], 0.0041729 secs] [Times: user=0.01 sys=0.00,real=0.01 secs]

[GC[1 CMS-initial-mark: 51172K(51200K)] 60269K(60416K), 0.0000883 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]

7、CMS收集器的运作步骤

CMS收集器是基于“标记-清除”算法实现的,它的运作过程相对于前面几种收集器来说要更复杂一些,整个过程分为6个步骤,包括:

初始标记(CMS initial mark)

并发标记(CMS concurrent mark)

并发预清理(CMS-concurrent-preclean)

重新标记(CMS remark)

并发清除(CMS concurrent sweep)

并发重置(CMS-concurrent-reset)

其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。其他动作都是并发的。

对应具体的运行结果:

[GC [ParNew: 9110K->972K(9216K),0.0056278 secs] 48708K->48702K(60416K), 0.0056664 secs] [Times: user=0.00sys=0.00, real=0.01 secs]

【1.初始标记】

[GC [1 CMS-initial-mark:47730K(51200K)] 48766K(60416K), 0.0000829 secs] [Times: user=0.00 sys=0.00,real=0.00 secs]

【2.并发标记】

[CMS-concurrent-mark: 0.003/0.003secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

【3.并发预清理】

[CMS-concurrent-preclean:0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

【4.重新标记】

[GC[YG occupancy: 1036 K (9216K)][Rescan (parallel) , 0.0001092 secs][weak refs processing, 0.0000140 secs][1 CMS-remark: 47730K(51200K)] 48766K(60416K), 0.0001609 secs] [Times:user=0.00 sys=0.00, real=0.00 secs]

【5.并发清除】

[CMS-concurrent-sweep: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

【6.并发重置】

[CMS-concurrent-reset: 0.000/0.000secs] [Times: user=0.00 sys=0.00, real=0.0