JVM⾯试必问的CMS,你懂了吗?
前⾔伤感的歌
虽然 CMS 已经是很古⽼的垃圾回收器了,⼤家现在动不动就G1、ZGC啥的,但是据我所了解,还是有很多公司的⽣产环境主要使⽤的 CMS,包括我⾃⼰呆过的⼏家⼤⼚也是。
因此在 JVM ⾯试中,CMS 也是问的最多的,包括我⾃⼰现在⾯试别⼈时,问到 JVM 这⼀块,我也喜欢从CMS开始,逐渐深⼊。
不多废话,今天我们就来盘他。
正⽂
1、什么是卡表(card table)?
试想⼀下,在进⾏ YGC 时,如何判断是否存在⽼年代到新⽣代的引⽤?
⼀个简单的办法是扫描整个⽼年代,但是这个代价太⼤了,因此 JVM 引⼊了卡表来解决这个问题。
卡表⼜称为卡⽚标记(card marking),由 Paul R. Wilson 和 Thomas G. Moher 在1989年发表的论⽂
⾥提出。
其原理为,在逻辑上将⽼年代空间分割为若⼲个固定⼤⼩的连续区域,分割出来的每⼀个个区域就称为卡⽚(card)。另外,为每个卡⽚准备⼀个与其对应的标记位,最简单的实现⽅案是由字节数组实现,以卡的编号作为索引。每个卡的⼤⼩通常介于128~512字节之间,⼀般使⽤2的幂字节⼤⼩,例如HotSpot使⽤512字节。
当卡⽚内部发⽣引⽤变化时(指针写操作),写屏障会将该卡在卡表中对应的字节标记为脏(dirty)。
有了卡表后,在 YGC 时,只需将卡表中被标记为 dirty 的 card 也作为扫描范围,就可以保障不扫描整个⽼年代也不会有遗漏了。
2、什么是 mod-union table?
通过上⾯对 card table 的介绍,我们知道 card table 会记录下⽼年代所有发⽣过引⽤变化对象所在的 card,⽽ CMS 在并发标记等阶段,也需要记录下⽼年代发⽣引⽤变化的对象以便后续重新扫描,是否可以直接复⽤ card table?
2002年属什么答案是不⾏的,这是因为每次 YGC 过程中都涉及重置和重新扫描 card table,这样是满⾜了 YGC 的
需求,但却破坏了CMS的需求,CMS 需要的信息可能被 YGC 给重置掉了。为了避免丢失信息,于是在 card table 之外另外加了⼀个 Bitmap 叫做 mod-union table。
在 CMS 并发标记正在运⾏的过程中,每当发⽣⼀次 YGC,当 YGC 要重置 card table ⾥的某个记录时,就会更新 mod-union table 对应的bit,相当于将 card table ⾥的信息转移到了 mod-union table ⾥。
这样,最后到 Final remark 的时候,card table 加 mod-union table 就⾜以记录在并发标记过程中⽼年代发⽣的所有引⽤变化了。
斯里巴加湾>孕妇能吃哪些食物3、CMS 垃圾收集的过程?
CMS 垃圾收集的过程⽹上通常有两个版本,4个步骤的和7个步骤的,两个版本其实都是对的。程序未响应
4个步骤应该主要是跟随周志明的说法,⽽ CMS 的相关论⽂其实也是按4个步骤介绍。
7个步骤则应该更多是从 CMS 的⽇志得出的说法,⽽7个步骤⾥其实也包含了上述的4个步骤,可以理解为7个步骤是更细的说法。
个⼈⽽⾔,我会更喜欢7个步骤的说法,因此这边介绍下7个步骤的过程。
1)初始标记(Initial Mark)
STW(stop the world),遍历 GC Roots,标记 GC Root 直达的对象。
我们以 GC 标记-清除算法为例简单的说明⼀下。
GC 开始运⾏前所有的对象都是⽩⾊。GC ⼀开始运⾏,所有从根能到达的对象都会被标记为灰⾊,然后被放到栈⾥。GC 只是发现了这样的对象,但还没有搜索完它们,所以这些对象就成了灰⾊对象。
灰⾊对象会被依次从栈中取出,其⼦对象也会被涂成灰⾊。当其所有的⼦对象都被涂成灰⾊时,该对象就会被涂成⿊⾊。当 GC 结束时已经不存在灰⾊对象了,活动对象全部为⿊⾊,垃圾则为⽩⾊。
下⾯是⼀个三⾊标记算法的⽰例动图,⼤家参考着理解。
有氧和无氧运动
明⽩了三⾊标记算法后,再回过头去看第5题,是不是顿时就明⽩了。
8、三⾊标记算法存在的问题?
三⾊标记算法是增量式垃圾回收算法,mutator可能会随时改变对象引⽤关系,因此在并发下会存在漏标和错标(多标)。
青岛小鱼山公园
1)漏标
直接通过⼀个简单的例⼦来看:
假设当GC线程执⾏到时刻1时,此时应⽤线程先执⾏了步骤1和2,也就是到了时刻3的场景,GC线程继续执⾏。高铁能带多少行李
此时对象Z只被⿊⾊对象X所引⽤,⽽⿊⾊对象是不会被继续扫描的,因此扫描结束后Z仍然是⽩⾊对象,也就是时刻4,此时⽩⾊对象Z则会被当做垃圾⽽回收。
2)错标(多标)
直接通过⼀个简单的例⼦来看:
假设当GC线程执⾏到时刻1时,此时应⽤线程先执⾏了步骤1,也就是到了时刻2的场景,GC线程继续执⾏。
此时对象Z是灰⾊对象,GC线程对其进⾏搜索,搜索结束后将其标记为⿊⾊,也就是时刻3,此时对象Z其实没有到GC Roots的引⽤,理应被回收,但是因为被错误的标记为⿊⾊,⽽在本次GC中存活了下来。