From cff84ef62857d6840dfb0a725c5e9b9217850f5f Mon Sep 17 00:00:00 2001 From: Ming Deng Date: Tue, 22 Jun 2021 22:49:55 +0800 Subject: [PATCH] update CMS --- gc/java_cms.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/gc/java_cms.md b/gc/java_cms.md index 301627b..a39d171 100644 --- a/gc/java_cms.md +++ b/gc/java_cms.md @@ -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 发生期间,不会出现老年代空间不足的问题。 @@ -139,13 +195,16 @@ CMS 的面试,大多数时候来说,就是面一下算法的大概流程, (接下来我们讨论解决方案,在前面提到过,就是`UseCMSCompactAtFullCollection` 和 `-XX:CMSFullGCBeforeCompaction` 参数)我们可以通过设置`UseCMSCompactAtFullCollection` 和 `-XX:CMSFullGCBeforeCompaction`参数来控制 CMS 执行压缩,并且控制在多少次 GC 触发一次压缩。 + #### 如何引导 - 讨论到正向的内存管理 - 讨论到操作系统内存管理 - 讨论到指针碰撞 +- 讨论到为什么要压缩 #### 类似问题 - 为啥老年代明明还有空闲内存,却触发了 Full GC?碎片太多,找不到足够大的连续的空闲内存;又或者 CMS 预估到接下来这点空闲内存不够了,需要提前触发GC(时间间隔轮询做这种判断) +- CMS 为什么要压缩 ### 什么时候会触发 Full GC? @@ -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) \ No newline at end of file +[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) \ No newline at end of file