老年代垃圾回收器CMS是如何工作的
CMS回收器使用的垃圾回收算法是标记-清理
,就是去找到那些没有
被GC Roots
直接或间接引用的对象,对他们进行多次标记,最后回收掉,标记-清理算法会产生大量的内存碎片
什么时候触发老年代GC?
-
MinorGC前,如果当前老年代的剩余空间小于新生代的对象总大小,并且没有设置
空间分配担保
JVM参数,直接触发Full GC -
在空间分配担保机制进行检查时,如果当前老年代剩余空间小于之前平均每次MinorGC后进入老年代的对象总大小时,触发Full GC
-
当Minor GC后,Survivor区中无法存放剩余存活对象,并且此时老年代剩余空间也不足以存放这些对象时,触发Full GC
-
老年代存活对象空间占比达到了JVM参数
“-XX:CMSInitiatingOccupancyFaction
设置的阈值(默认为92%),触发Full GC
CMS回收器进行垃圾回收的步骤
分为四步
- 初始标记
- 并发标记
- 重新标记
- 并发清理
其中,初始标记和重新标记阶段会出现STW
,因为STW会让系统停止对外服务,如果此时标记时间过长,会严重影响系统性能,因此CMS回收器使用垃圾回收线程和系统工作线程尽量同时执行的模式
来处理的
初始标记
CMS进行第一次标记,触发STW
,这个阶段的速度比较快,因为这次标记只标记那些被GC Roots
直接引用的对象
GC Roots
一般有局部变量,静态变量等,不包含实例变量
并发标记
此时,工作线程和GC线程是同时工作的,系统可以对外提供服务
该阶段GC线程会尽可能对已有的对象进行GC Roots
追踪,包括那些被间接引用的对象
这个阶段非常耗时,但因为是和系统工作线程并发的,所以不会影响系统性能
重新标记
这个阶段也会触发STW
,由于在并发标记期间,系统还在不断的创建新的对象,因此内存中还会有许多存活对象和垃圾对象是没有被标记出来的
此时会重新标记那些存活的对象
这里标记的速度也非常快,因为只需要对少数的对象进行重新标记即可
并发清理
该阶段就是回收掉垃圾对象,比较耗时,但是因为没有STW
,所以不会对系统性能造成影响
为什么老年代的垃圾回收特别慢?
-
CMS执行GC时,会有两个并发阶段,此时工作线程和GC线程同时工作,GC线程不能充分利用CPU资源,CMS默认设置的GC线程数量是
(CPU核数 + 3) / 4
个 -
在进行并发清理时,会产生不少
浮动垃圾(工作线程会让一些对象进入老年代,同时这些对象没多久就成为垃圾对象了)
,但此时CMS无法清理这些对象,如果在这个期间,新生代的存活对象要进入老年代并且此时老年代剩余空间不足,还会触发concurrent mode failure
,此时系统会自动启动serialOld
回收器,触发STW
,同时再执行一次标记清理的过程 -
Full GC不仅仅采用了标记清理算法,因为标记清理算法会产生大量的内存碎片,这样会导致老年代中连续可用的空间变少,增加触发Full GC的概率,因此CMS有一个默认的JVM参数
-XX:+UseCMSCompactAtFullCollection
,在每次Full GC后要触发STW
,来整理这些内存碎片,通过JVM参数-XX:CMSFullGCsBeforeCompaction
可以设置执行多少次Full GC后进行内存整理,默认为0,即每次Full GC后都进行整理 -
老年代进行标记时,不同与新生代,由于大部分对象都是存活的,所以消耗的时间比较长