Skip to content

Commit

Permalink
update CMS
Browse files Browse the repository at this point in the history
  • Loading branch information
flycash committed Jun 22, 2021
1 parent a1e6107 commit cff84ef
Showing 1 changed file with 102 additions and 1 deletion.
103 changes: 102 additions & 1 deletion gc/java_cms.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,62 @@ CMS 的面试,大多数时候来说,就是面一下算法的大概流程,
#### 类似问题
- CMS 如何处理年轻代?年轻代是用 Parallel New 来回收的,CMS 会利用年轻代 GC的结果来减少初始标记和再标记两个停顿过程

### CMS 并发标记阶段,如果引用发生了变更会怎样?

分析:这个要分开来说。首先 CMS 是只回收老年代和永久代的,那么年轻代对象之间的引用变更,CMS 是不会管的。因为后面 CMS 在最终的重标记阶段,还要重新扫描年轻代,所以这个阶段不用管。

而其它引用变更,大体上又分为两类,一个是年轻代引用指向老年代的引用变更,一个是老年代内部之间的引用变更。无论是这两种情况的哪一种,CMS 只会把内存标记为 Dirty,后面预清理阶段会进行处理。

答案:如果是年轻代之间对象应用发生变更,那么 CMS 在这个阶段不会做什么(这里引导面试官问你,最终 CMS 是在哪里处理的)。否则,CMS 会把内存标记为 Dirty,在预清理阶段和重标记阶段再次处理。

#### 类似问题
- CMS 并发标记阶段,创建了新对象,会发生什么?什么也不会发生,因为 CMS 不管年轻代,只是在后面重标记阶段重新标记年轻代对象;
- CMS 并发标记阶段,如果老年代引用发生了变化,会发生什么?CMS会重新把这块内存标记为 dirty,预清理和重标记阶段再处理一遍;
- CMS 并发标记阶段,如果年轻代指向老年代的引用发生了变化,会发生什么?和上面一样,把这块内存标记为脏,预清理和重标记阶段再来一遍;

### CMS 的预清理阶段做了什么?

分析:这个阶段看起来比较花里胡哨,具体来说,它可以是单独的预清理(Pre-clean)阶段,也可以是指预清理和可中断预清理两个阶段。

预清理的目标是降低停顿时间。它是为了解决并发标记阶段引用变化问题而引入的。从前面也可以看出来,CMS 一个重要的特征就是并发标记阶段,会发生应用变更。非年轻代的引用变更,CMS 会把内存标记为 Dirty。

如果我们考虑一下,要是没有预清理阶段,那么重标记阶段就需要全部 Dirty 的内存再标记一遍,如果应用很繁忙,那么可能很多内存都需要重新标记。

因此引入了并发的预清理阶段,这一个阶段就是处理这些 Dirty 的内存块。然后发现,这个预清理也是并发的,如果这个时候也有引用变更,岂不是又产生了新的 Dirty 内存块?是的,但是因为这个过程比较快,所以理论上产生的 Dirty 块要比较少。后面的可中断的预处理阶段,也是这么一个过程,但是为了避免无线套娃,所以它是可中断的。

答案:预清理阶段主要是处理在并发阶段标记为 Dirty 的内存块(具体来说,是卡表里面的卡被标记为 Dirty)。提前处理了这些 Dirty 内存块,那么重标记阶段就可以少处理一点内容,减轻停顿时间。

预清理阶段有一个可中断的预清理。该阶段是不断循环,循环内的步骤类似预清理阶段,不过除了处理 Dirty 的内存块,还会扫描年轻代,找到指向老年代的引用。

(进阶,讨论中断的条件)这个阶段是可以被中断的,中断的条件有三个:
1. 循环次数达到阈值;
2. 执行时间达到了阈值;
3. 新生代的 Eden 的内存使用率达到了阈值;

(这三个分别是三个参数控制,不过不太重要,不需要记住参数名字,前两个很好理解容易记忆,后一个记不住也没关系)

#### 类似问题
- 什么是预清理?
- 什么是可中断预清理?
- 什么时候会中断预清理?
- 为什么要中断?

### 重标记阶段,做了什么?

分析:重标记阶段主要是找出来并发阶段修改的引用,再一次处理一遍。

所以重标记不得不重新扫描 GC root、年轻代和 Dirty 内存块。

注意的是,重标记不会重新完全标记一遍,而是尽量缩小了标记范围。

答案:重标记阶段主要是为了处理并发阶段发生变更的引用。该阶段主要是重新扫描 GC root,年轻代和 Dirty 块。

(我们稍微讨论一下重标记停顿时间来源)大多数时候,该阶段的停顿时间主要是由扫描年轻代和 Dirty 造成的。因此为了降低这个阶段的停顿时间,我们可以调整预清理的参数,尽可能多在预清理阶段处理完,也可以开启参数`CMSScavengeBeforeRemark`,强制在重标记之前执行一次年轻代GC,以降低GC时间。

#### 类似问题
- 怎么降低重标记的停顿时间
- 为什么要在重标记之前执行一次年轻代GC

### CMS 来不及回收老年代会发生什么事情

分析:这其实考察的是 CMS的一个异常情况。回忆一下,什么时候会触发 CMS?除了第一次以外,其他时候都是JVM觉得应该GC了,就会触发GC。它会控制住GC频率,确保 CMS GC 发生期间,不会出现老年代空间不足的问题。
Expand Down Expand Up @@ -139,13 +195,16 @@ CMS 的面试,大多数时候来说,就是面一下算法的大概流程,

(接下来我们讨论解决方案,在前面提到过,就是`UseCMSCompactAtFullCollection``-XX:CMSFullGCBeforeCompaction` 参数)我们可以通过设置`UseCMSCompactAtFullCollection``-XX:CMSFullGCBeforeCompaction`参数来控制 CMS 执行压缩,并且控制在多少次 GC 触发一次压缩。


#### 如何引导
- 讨论到正向的内存管理
- 讨论到操作系统内存管理
- 讨论到指针碰撞
- 讨论到为什么要压缩

#### 类似问题
- 为啥老年代明明还有空闲内存,却触发了 Full GC?碎片太多,找不到足够大的连续的空闲内存;又或者 CMS 预估到接下来这点空闲内存不够了,需要提前触发GC(时间间隔轮询做这种判断)
- CMS 为什么要压缩

### 什么时候会触发 Full GC?

Expand Down Expand Up @@ -173,6 +232,48 @@ CMS 的面试,大多数时候来说,就是面一下算法的大概流程,
- 反正讨论到了 CMS,Full GC 都可以聊这个
- 可以结合 G1 垃圾回收器,来对比两者的触发时机

### CMS 采用的空闲链表方式管理内存,存在什么缺点?

分析:本质上,这应该算是内存管理的面试题,因为别的,例如操作系统,如果也采用了空闲链表,那么也会有这个问题。

就是碎片和内存分配低效两个问题。

答案:主要有两个方面,内存碎片和内存分配低效。

(如果之前没有聊过内存碎片,这里可以补充一下什么叫做内存碎片)CMS 的内存管理方式会导致这么一个问题,内存被分割为一个个零散的小块,这些小块可能太小,以至于无法容纳任何对象,导致内存利用率低。

而空闲链表这种管理方式,导致内存分配的时候,需要查找可用内存,然后再分配,效率极低。

#### 类似问题
- 为什么CMS分配内存比较慢
- CMS用了指针碰撞技术吗?显然没有

#### 如何引导
- 聊到空闲链表都可以说

### CMS 有什么缺点

分析:CMS 的缺点是极为明显的。最容易想到的就是内存碎片问题,不过很少有人会回答道内存碎片带来的影响。

另外就是 CMS 会出现并发模式失败的可能,也就是退化为 Serial GC 的可能性。

最好就是 CMS 的停顿时间,比 G1 之类的长。它的两次标记,初始标记和重标记,都和年轻代存活对象有关,因此为了避免 CMS 的停顿时间,我们会尝试开启一个参数:
`CMSScavengeBeforeRemark` 在重标记之前执行依次年轻代 GC,这样能显著降低 GC 停顿时间。也因此 CMS 不适用于大堆(有些人说 6G 以上,有些人说 8G 以上就不要用 CMS了,我觉得是跟应用特征相关)

答案:CMS主要有三个缺点:
1. 内存碎片。这导致 CMS 内存使用率比较低(总有一些碎片无法被利用),(补充空闲链表,作为亮点)而且 CMS 使用了空闲链表来管理空闲内存,导致内存分配相比指针碰撞,要慢很多;
2. CMS 存在并发模式失败的可能,这会导致 CMS 退化为 Serial GC,性能极差;(这里可能面试官问什么时候会导致并发模式失败)
3. CMS 不适用大堆。CMS 的停顿时间相比 G1 还是比较长的,(补充一下重标记的问题,引导面试官问重标记问题)我们可以通过开启`CMSScavengeBeforeRemark`来确保重标记之前,能够触发一次 Young GC,来减轻 CMS 的停顿时间

#### 如何引导
- 讨论到并发模式失败
- 讨论到重标记
- 讨论到空闲链表

#### 类似问题
- CMS 适合什么场景

## Reference
[Oracle documents - CMS](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html)
[Java gc roots](https://help.eclipse.org/2021-06/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html&cp=37_2_3)
[Java gc roots](https://help.eclipse.org/2021-06/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html&cp=37_2_3)
[CMS](https://juejin.cn/post/6844903740864987149)

0 comments on commit cff84ef

Please sign in to comment.