1.CMS的两种模式与一种特殊策略
1.1Backgroud CMS(没有并发失败的情况)
1.1.1并发标记还能被整理成两个流程
(1)初始标记
(2)并发标记(3)(4)在这个阶段发生
(3)并发预处理
(4)可中止的预处理
(5)重新标记
(6)并发清除
1.1.2为什么我们的并发标记细化之后还会额外有两个流程出现呢?
讨论这个问题之前,我们先思考一个问题,假设CMS要进行老年代的垃圾回收,我们如何判断被年轻代的对象引用的老年代对象是可达对象。
当老年代被回收的时候,我们如何判断A对象是存活对象。
必须扫描新生代来确定,所以CMS虽然是老年代的垃圾回收器,却需要扫描新生代的原因。
既然这个时候我需要扫描新生代,那么全量扫描会不会很慢
答:肯定会的 ,但是接踵而来的问题:既然会很慢,我们的停顿时间很长,可是CMS的目标是什么,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。这不是与他的设计理念不一致吗?
1.1.3那怎么让我们的回收变快?
肯定是垃圾越少越快。所以我们的CMS想到了一种方式,就是我先进行新生代的垃圾回收,也就是一次young GC,回收完毕之后。是不是我们新生代的对象就变少了,那么我再进行垃圾回收,是不是就变快了。可以通过CMS提供的两个参数控制垃圾的回收。
CMSScheduleRemarkEdenSizeThreshold 默认值:2M
CMSScheduleRemarkEdenPenetration 默认值:50%
这两个参数组合起来就是预清理之后,Eden空间使用超过2M的时候启动可中断的并发预清理,(CMS-concurrent-abortable-preclean),到Eden空间使用率到达50%的时候中断(但不是结束),然后进入Remark(重新标记阶段)。
1.1.4这里面有个概念,为什么并发预处理前面会有可中断的意思
可中断意味着,假设你一直在预处理,预处理是干什么,无非就是去帮你把正式应该处理的前置工作给做了。所以他一定干了很多事情,但是这些事情迟早有个头,所以就设置了一个时间对他进行打断。所以,并发预处理的逻辑是当你发生了minor GC ,我就预处理结束了但是,我怎么知道你什么时候发生minor GC?
答案是我不知道,垃圾回收是JVM自动调度的,所以我们无法控制垃圾回收,那我不可能无限制的执行下去,总要有个结束时间吧,所以CMS提供了一个参数
CMSMaxAbortablePrecleanTime ,默认为5S
只要到了5S,不管发没发生Minor GC,有没有到CMSScheduleRemardEdenPenetration都会中止此阶段,进入remark,如果在5S内还是没有执行Minor GC怎么办?CMS提供一个参数
CMSScavengeBeforeRemark参数,使remark前强制进行一次Minor GC。
1.2老年代的策略:记忆集解决每次oldGC都要yuongGC的问题
当我们进行young gc时,可作为gc roots的东西除了常见的栈引用、静态变量、常量、锁对象、class对象这些常见的之外,如果老年代有对象引用了我们的新生代对象,那么老年代的对象也应该加入gc roots的范围中,但是如果每次进行young gc我们都需要扫描一次老年代的话,那我们进行垃圾回收的代价实在是太大了,因此我们引入了一种叫做记忆集的抽象数据结构来记录这种引用关系。
记忆集是一种用于记录从非收集区域指向收集区域的指针集合的数据结构。
如果我们不考虑效率和成本问题,我们可以用一个数组存储所有有指针指向新生代的老年代对象。但是如果这样的话我们维护成本就很好,打个比方,假如所有的老年代对象都有指针指向了新生代,那么我们需要维护整个老年代大小的记忆集,毫无疑问这种方法是不可取的。因此我们引入了卡表的数据结构
1.2.1卡表
记忆集是我们针对于跨代引用问题提出的思想,而卡表则是针对于该种思想的具体实现。(可以理解为记忆集是结构,卡表是实现类)
在hotspot虚拟机中,卡表是一个字节数组,数组的每一项对应着内存中的某一块连