CMS回收流程
CMS收集器时一种以获取最短回收停顿时间为目标的收集器
采用的是“标记-清除算法”整个过程分为4步
(1)初始标记 CMS initial mark 标记GC Roots直接关联对象,不用关联整个GC Roots链,速度很快
(2)并发标记 CMS concurtrent mark 进行GC Roots 整个链路的关联,因为速度很慢,所以采用并发的方式,不影响用户线程
(3)重新标记 CMS remark 修改并发标记因为用户程序变动的内容
(4)并发清除 CMS concurrent sweep 清除不可达对象的回收空间,同时有新垃圾产生,留着下次清除称为浮动垃圾
由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发的执行的
优点:并发收集,低停顿
缺点:产生大量空间碎片,并发阶段会降低吞吐量
第一个问题:为什么CMS回收流程图上初始标记是单线程,为什么不适用多线程呢?
初始化标记阶段是串行的,这是JDK7的行为
JDK8以后默认是并行的
可以通过参数 -XX:+CMSParallelInitialMarkEnabled控制是否串行
CMS的两种模式与一种特殊策略
Backgroud CMS:
实际上我们的并发标记还能被整理成两个流程
(1)初始标记
(2)并发标记
(3)并发预处理
(4)可中止的预处理
(5)重新标记
(6)并发清除
为什么我们的并发标记细化之后还会额外有两个流程出现呢?
讨论这个问题之前,我们先思考一个问题,假设CMS要进行老年代的垃圾回收,我们如何判断被年轻代的对象引用的老年代的对象是可达对象
问题一、也就是这张图,当老年代被回收的时候,我们如何判断A对象是存活对象?
这个时候必须扫描新生代来确定,所以CMS虽然是老年代的垃圾收集器,却需要扫描新生代的原因
问题二、既然这个时候我们需要扫描新生代,那么全量扫描会不会很慢?
肯定是会的,但是CMS是以获取最短停顿时间为目标,这样的结果和他的目标是不一致的
思考:怎么才能让我们的回收变快
肯定是来及越少越快,所以CMS想到了一种方式,就是我们先进行新生代的垃圾回收,也就是一样young GC,回收完毕之后,我们新生代的对象就变少了,那么我们在进行垃圾回收,就会变快了!
所以CMS有两个参数
CMSScheduleREemarkEdenSizeThreshold 默认值:2M
CMSScheduleRemarkEdenPenetration 默认值:50%
这两个参数组合起来就是预清除之后,Eden空间使用超过2M的时候启动可终端的并发预清理,到Eden空间使用率达到50%的时候中断(但不是结束),进入Remark(重新标记阶段)
这里有一个概念,为什么并发预处理前面会有可中断几个字,什么意思?
可中断意味着,假设你一直在预处理,预处理是干什么, 无非就是去帮你把正式应该处理的前置工作给做了,所以它一定干了很多事情,但是这些事情迟早有个头,所以就设置一个时间对他进行打断,所以并发预处理的逻辑是当你发生了minor GC预处理就结束了
这里有个问题,我怎么知道什么时候发生minor GC?
minor GC是jvm自动调度的,,所以我们无法知道什么时候发生minor GC,那我不可能无限制的执行下去,总要有个结束时间
CMS提供了一个参数CMSMaxAbortablePrecleanTime, 默认为5s
只要到了5S,不管发没发生Minor GC,有没有到CMSScheduleRemardEdenPenetration都会中止此阶