CMS垃圾回收器
简介
CMS全称Concurrent-Mark-Sweep。
在JDK1.5时期,HotSpot推出了一款在强交互应用中有划时代意义的垃圾收集器,这款收集器是HotSpot中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程和用户线程同时工作。
CMS的垃圾收集算法采用标记-清除算法,也会STW。
CMS作为老年代垃圾收集器,却无法与新生代中收集器Parallel Scavenge配合工作。
G1出现之前,CMS的使用非常广泛。
主要环节
CMS的整个过程分为4个阶段:初始标记、并发标记、重新标记、并发清除。
注意上图,初始标记和重新标记环节是需要STW的。
初始标记(Initial-Mark)
在该阶段,程序中所有线程都会因为STW机制短暂暂停。该阶段的主要任务仅仅是标记出GC Roots能够直接关联的对象。一段标记完成就会恢复被暂停的所有用户线程。由于直接关联对象数量较少,所以该阶段速度非常快。
并发标记(Concurrent-Mark)
从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。
重新标记(Remark)
在该阶段,程序中所有线程都会因为STW机制短暂暂停。该阶段的主要任务是修正因为用户线程的运作而导致标记变动的那一部分对象的标记记录。这个阶段的停顿时间通常比初始标记稍长一些,但也远比并发标记阶段时间短。
并发清除(Concurrent-Sweep)
清理删除掉标记阶段判断为已经死亡的对象,释放内存空间。由于不需要移动存活对象,因此该阶段也可以与用户线程同时并发,同时也会产生内存碎片。
CMS的特点
低延时:由于最耗时的操作都不需要暂停工作,所以整体的回收时低停顿的。
由于垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保程序用户线程有足够的内存可用。因此,CMS收集器不能想其它收集器那样等到老年代几乎被填满了在进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够内存空间支持程序运行。
要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”,这是虚拟机需要启动后备方案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了【当然,同时也会清理因为CMS产生的内存碎片】。
CMS使用的时标记-清除算法,不可避免地会产生内存碎片。那么CMS在为新对象分配内存时,将只能使用空闲列表,而无法使用指针碰撞。
优点
- 并发收集
- 低延迟
缺点
- 会产生内存碎片:在无法分配大对象的情况下,会提前触发Full GC。
- CMS收集器堆CPU资源非常敏感:它虽然不会导致用户线程停顿,但是因为CMS会占用一部分线程而导致应用程序变慢,总吞吐量变低。
- CMS无法处理浮动垃圾:在并发阶段产生的新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收。
讨论:重新标记为何无法回收浮动垃圾?
并行标记阶段标记的是所有的可达对象,之后的清除阶段将所有可达对象以外的内存区域进行清理。
然而,在用户线程执行过程中,并行标记阶段扫描过的区域中可能会产生新的可达对象。为了避免将这些对象被错误回收,重新标记阶段会再次检查标记为不可达的内存区域,并将其中的可达对象进行标记,进而避免程序出错。
但是,重新标记阶段并不会检查已经标记为可达的对象,即使部分对象在之后变为不可达的。
因此,重新标记无法处理浮动垃圾。
如何使用
启用CMS
-XX:+UseConcMarkSweepGC
:指定使用CMS收集器为老年代垃圾回收器。
- 该参数会自动将
-XX:+UseParNewGC
开启,即新生代使用ParNew
参数设置
-XX:CMSInitiatingOccupanyFraction
:设置堆内存使用率阈值,一旦达到该阈值,便开始垃圾回收。
- 默认值:JDK5及以前为68,JDK6及以后为92
- 如果内存增长缓慢,可以设置一个稍大值,进而降低CMS触发频率;如果内存增长很快,应该设置一个较小值,进而降低Full GC的次数。
-XX:+UseCMSCompactAtFullCollection
用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。
-XX:CMSFullGCsBeforeCompaction
:设置在执行多少次Full GC后对内存空间进行压缩整理。
-XX:ParallelCMSThreads
:设置CMS的线程数量。
- 默认值为 ( P a r a l l e l G C T h r e a d s + 3 ) / 4 (ParallelGCThreads+3)/4 (ParallelGCThreads+3)/4,中 P a r a l l e l G C T h r e a d s ParallelGCThreads ParallelGCThreads为年轻代并行收集器的线程数量。
CMS在JDK中的变化
JDK9中,被弃用,如果强行使用会报一个警告,但是依然可以使用。
JDK14中,被移除,如果强行使用会报一个警告,并使用默认GC。