持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情
CMS分为初始标记、并发标记、重新标记、并发清理四个阶段,其中重新标记则用到了三色标记法
我们先来详细了解一下三色标记法。三色标记法中将对象分为白色、灰色、黑色的过程。
- 白色:白色是对象默认的颜色,从GC Root开始扫描,如果不可达的对象的就是白色,在并发清理阶段就会被清理掉。
- 灰色:灰色表示当前对象已经被扫描,但是当前对象所依赖的其他对象还没有被扫描。
- 黑色:黑色表示当前对象和它所依赖的对象都已经被扫描过。
那它又是怎么产生多标和漏标的呢?下面来画图看看:
开始有三个对象,分别是对象1和对象2以及对象3,三个对象与GC Root之间都存在引用链,当开化寺标记,就会从GC Root开始扫描。
当扫描了对象1和对象2的时候,因为对象2没有再依赖的引用,所以它会变成黑色,而对象1还引用着对象3,并且对象3还没有扫描,所以对象1变成灰色。
若是,此时用户线程将对象3与对象1之间的引用关系改变了,变成了对象2余对象3之间有引用关系,因为对象2已经扫描完了,对象3还没扫描,此时应该是对象2是灰色的状态,并且对象3是白色的状态,对象3就会被回收掉,这就出现了漏标的情况。
多标的情况就是当对象1和对象3之间开始有引用链,并且都已经标记为黑色,此时用户线程又把对象3设置为null,那么此时按理来说对象3应该被回收的,但是因为是黑色并不会被回收掉,所以出现了多标;
多标的情况可以在下次垃圾回收的时候,进行重新标记,被重新回收,所以多标并不会是GC回收的过程出现bug。
而漏标就需要解决了,不然GC回收就会出现bug,对于漏标CMS给出的解决方案是增量更新的方法。它的原理就是假如对象3的引用从对象1变成了对象2,那么对象2就会变成灰色,并且对象2会被集合里面,在重新标记的阶段以对象2为根节点向下扫描。
这样CMS就解决漏标的问题,并且实现了整个GC Root对象图的时候,能够与用户线程并发执行,大大减少了STW的时间。
那为什么CMS又选择标记-清除算法呢?因为假如选择标记-整理算法,在并发清理阶段因为要进行整理,涉及对象的移动,此时就不能与用户线程一起并发操作,这样清理阶段就必须STW,就违背了CMS设计初衷:获取最短回收停顿时间。
与CMS有关的JVM参数如下所示:
- -XX:+UseConcMarkSweepGC:使用CMS垃圾收集器(当设置这个参数后,年轻代默认会开启ParNew)。
- -XX:+UseCMSCompactAtFullCollection:用于在CMS收集器不得不进行FullGC时开启内存碎片的合并整理过程,由于这个内存整理必须移动存活对象,清理阶段是无法并发的,此参数从JDK9开始废弃。
- -XX:CMSFullGCsBefore-Compaction:多少次FullGC之后压缩一次,默认值为0,表示每次进入FullGC时都进行碎片整理,此参数从JDK9开始废弃。
- -XX:CMSInitiatingOccupancyFraction:当老年代使用达到该比例时会触发FullGC,默认是92。
- -XX:+UseCMSInitiatingOccupancyOnly:这个参数搭配上面那个用,表示是不是要一直使用上面的比例触发FullGC,如果设置则只会在第一次FullGC的时候使用-XX:CMSInitiatingOccupancyFraction的值,之后会进行自动调整。
- -XX:+CMSScavengeBeforeRemark:在FullGC前启动一次MinorGC,目的在于减少老年代对年轻代的引用,降低CMSGC的标记阶段时的开销,一般CMS的GC耗时80%都在标记阶段。
- -XX:+CMSParallellnitialMarkEnabled:默认情况下初始标记是单线程的,这个参数可以让他多线程执行,可以减少STW。
- -XX:+CMSParallelRemarkEnabled:使用多线程进行重新标记,目的也是为了减少STW。