CMS 垃圾回收器

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qzqanzc/article/details/88192195

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集
器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重
视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常
符合这类应用的需求。

从名字(包含“Mark Sweep”)上就可以看出,CMS收集器是基于“标记—清除”算法实现
的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤,包括:

  1. 初始标记(CMS initial mark)
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark)
  4. 并发清除(CMS concurrent sweep)

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

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起
工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。通
过图1可以比较清楚地看到CMS收集器的运作步骤中并发和需要停顿的时间。
在这里插入图片描述

CMS是一款优秀的收集器,它的主要优点在名字上已经体现出来了:并发收集、低停
顿,Sun公司的一些官方文档中也称之为并发低停顿收集器(Concurrent Low Pause
Collector)。但是CMS还远达不到完美的程度,它有以下3个明显的缺点:
CMS收集器对CPU资源非常敏感。其实,面向并发设计的程序都对CPU资源比较敏感。
在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资
源)而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程数是(CPU数量
+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且
随着CPU数量的增加而下降。但是当CPU不足4个(譬如2个)时,CMS对用户程序的影响就
可能变得很大,如果本来CPU负载就比较大,还分出一半的运算能力去执行收集器线程,就
可能导致用户程序的执行速度忽然降低了50%,其实也让人无法接受。为了应付这种情况,
虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的
CMS收集器变种,所做的事情和单CPU年代PC机操作系统使用抢占式来模拟多任务机制的思
想一样,就是在并发标记、清理的时候让GC线程、用户线程交替运行,尽量减少GC线程的
独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得少一些,
也就是速度下降没有那么明显。实践证明,增量时的CMS收集器效果很一般,在目前版本
中,i-CMS已经被声明为“deprecated”,即不再提倡用户使用。
CMS收集器无法处理浮动垃圾(Floating Garbage),可能出现“Concurrent Mode
Failure”失败而导致另一次Full GC的产生。由于CMS并发清理阶段用户线程还在运行着,伴
随程序运行自然就还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法
在当次收集中处理掉它们,只好留待下一次GC时再清理掉。这一部分垃圾就称为“浮动垃
圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间
给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进
行收集,需要预留一部分空间提供并发收集时的程序运作使用。在JDK 1.5的默认设置
下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在
应用中老年代增长不是太快,可以适当调高参数-XX:CMSInitiatingOccupancyFraction的值来
提高触发百分比,以便降低内存回收次数从而获取更好的性能,在JDK 1.6中,CMS收集器
的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一
次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来
重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CM
SInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure”失败,性能
反而降低。
还有最后一个缺点,在本节开头说过,CMS是一款基于“标记—清除”算法实现的收集
器,如果读者对前面这种算法介绍还有印象的话,就可能想到这意味着收集结束时会有大量
空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有
很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full
GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开
关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并
整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。
虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,这个参数是用于
设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入Full
GC时都进行碎片整理)。

猜你喜欢

转载自blog.csdn.net/qzqanzc/article/details/88192195
今日推荐