
可以把CMS 和 G1理解成两代不同思想的垃圾回收器CMS 的核心目标是尽量缩短单次 STW 时间。G1 的核心目标是在可控停顿时间内尽量回收更多垃圾。一、CMS 垃圾回收器CMS 全称是Concurrent Mark Sweep中文一般叫并发标记清除垃圾回收器。它主要用于老年代回收。它的设计目标是响应时间优先尽量减少 STW 时间。也就是说CMS 更关心的是用户线程不要停太久。比如一些 Web 系统、接口服务如果一次 GC 停顿几秒用户请求就会明显卡顿所以 CMS 希望把 GC 的大部分工作和用户线程并发执行。二、CMS 的回收过程CMS 的完整过程主要有四步初始标记 - 并发标记 - 重新标记 - 并发清理1. 初始标记STW初始标记会暂停用户线程。它只标记一小部分对象GC Roots 能直接关联到的对象。比如线程栈中的引用 静态变量引用 常量引用 JNI 引用这些根对象直接能找到的对象会被先标记出来。因为这个阶段只标记直接关联对象所以速度很快STW 时间较短。2. 并发标记不需要 STW并发标记阶段GC 线程和用户线程同时执行。它会从初始标记阶段找到的对象继续往下遍历找到所有可达对象。比如GC Roots ↓ 对象 A ↓ 对象 B ↓ 对象 C如果 A、B、C 都能从 GC Roots 间接访问到那它们都不是垃圾。这个阶段耗时较长但因为它和用户线程并发执行所以用户程序不会完全停下来。这就是 CMS 停顿时间短的重要原因之一。3. 重新标记STW问题来了。并发标记的时候用户线程还在运行。用户线程可能会修改对象之间的引用关系。比如原来是A - B并发标记过程中用户线程改成了A - C这时候 GC 线程之前看到的对象关系可能已经过时了。所以 CMS 需要进入重新标记阶段再次暂停用户线程修正并发标记期间发生变化的引用关系。你写的“漏标”这个理解是对的但可以说得更准确一点重新标记主要是为了修正并发标记期间由于用户线程继续运行导致的标记变化避免把仍然存活的对象误判为垃圾。注意GC 最怕的是把活对象当垃圾回收掉这会导致程序错误。4. 并发清理不需要 STW标记完成后哪些对象是活的哪些对象是垃圾就已经知道了。然后 CMS 会清理那些没有被标记的对象。这个阶段也是并发执行的GC 线程和用户线程同时工作。但是 CMS 使用的是标记-清除算法。所以它只是把垃圾对象占用的空间释放掉并不会移动存活对象。三、CMS 为什么 STW 时间短主要有两个原因。第一CMS 把耗时最长的两个阶段做成了并发并发标记 并发清理这两个阶段不需要长时间暂停用户线程。第二CMS 使用的是标记-清除算法。标记-清除不需要移动对象只需要把垃圾对象清掉所以清理速度相对较快。所以 CMS 的整体特点是停顿时间短 响应速度好 适合低延迟系统四、CMS 的缺点CMS 的缺点也很明显主要有三个。1. 内存碎片问题CMS 使用的是标记-清除算法。标记-清除不会整理内存。比如老年代原来是这样的[对象][垃圾][对象][垃圾][对象][垃圾]清理之后变成[对象][空闲][对象][空闲][对象][空闲]虽然空闲空间很多但这些空间是不连续的。这就会产生内存碎片。假设现在有很多小空闲块2MB 3MB 4MB 5MB总共 14MB 空间。但是如果你要分配一个 10MB 的大对象就可能失败因为没有一块连续的 10MB 空间。于是就可能触发 Full GC。2. 浮动垃圾问题CMS 在并发清理阶段用户线程还在运行。用户线程运行过程中还可能继续产生新的垃圾。这些垃圾是在 CMS 标记完成之后才产生的所以本轮 CMS 没有办法处理它们。这些垃圾就叫浮动垃圾。比如CMS 已经完成标记 用户线程继续运行 用户线程又产生了一批垃圾这批垃圾只能等下一次 GC 再回收。如果浮动垃圾太多而老年代空间又不足就可能出现Concurrent Mode Failure并发模式失败。这时候 CMS 就撑不住了需要退化为 Full GC。3. 并发执行会抢 CPUCMS 的并发标记和并发清理虽然不会完全暂停用户线程但 GC 线程和用户线程同时运行会抢 CPU 资源。如果服务器 CPU 核数比较少CMS 可能会影响用户线程的执行效率。所以 CMS 不是完全没有代价的。五、CMS 退化为 Serial Old 的问题当 CMS 出现问题比如老年代空间不足 浮动垃圾太多 内存碎片严重 大对象分配失败就可能触发 Full GC。CMS 的 Full GC 通常会退化为Serial Old。Serial Old 是单线程老年代垃圾回收器。它会STW 单线程回收 使用标记-整理算法标记-整理算法会移动对象整理内存碎片。比如[对象][空闲][对象][空闲][对象]整理后变成[对象][对象][对象][空闲][空闲]这样可以解决内存碎片问题。但是代价是STW 时间非常长。所以 CMS 的最大风险就是平时停顿很短但一旦 Full GC停顿可能非常严重。六、G1 垃圾回收器G1 全称是Garbage First。意思是优先回收垃圾最多的区域。G1 的设计目标是低延迟 高吞吐 可预测停顿时间 适合大堆内存和 CMS 不同G1 不只是老年代回收器它是一个面向整个堆的垃圾回收器。七、G1 的堆内存结构传统垃圾回收器一般把堆分成新生代 老年代比如Eden Survivor Old而 G1 把整个堆切成很多个大小相等的小块。这些小块叫Region。比如整个堆被切成这样Region 1 Region 2 Region 3 Region 4 Region 5 ...每个 Region 可以动态扮演不同角色Eden Region Survivor Region Old Region Humongous RegionHumongous Region 用来存放大对象。所以 G1 不是完全不要分代而是逻辑上仍然有新生代、老年代但物理上不再是连续的大块空间而是由一个个 Region 组成。这一点很重要。你写的“分区取代分代”可以稍微修正成G1 不是取消分代而是用 Region 作为基本管理单位让新生代和老年代都由一组不连续的 Region 组成。八、G1 为什么可以控制 STW 时间G1 有一个很重要的参数-XX:MaxGCPauseMillis比如设置为200ms意思是希望 GC 停顿时间尽量控制在 200ms 左右。注意是“尽量”不是绝对保证。G1 的做法是不一定每次都回收整个堆而是选择部分收益最高的 Region 回收。比如现在有这些 RegionRegion A垃圾 80% Region B垃圾 70% Region C垃圾 20% Region D垃圾 10%如果暂停时间预算有限G1 会优先选择Region A Region B因为它们垃圾最多回收收益最高。这就是 Garbage First 的含义垃圾最多的 Region 优先回收。所以 G1 的思路是有限的停顿时间内 选择最值得回收的 Region 尽量回收更多空间九、G1 的回收过程G1 的回收过程可以分成两类Young GC Mixed GC你写的那套过程更接近 G1 的并发标记周期和混合回收过程。整体可以这样理解初始标记 - 并发标记 - 最终标记 - 筛选回收/混合回收1. 初始标记STW初始标记也是标记 GC Roots 直接关联的对象。这个阶段需要 STW但时间很短。在 G1 中初始标记通常会借助一次 Young GC 一起完成。2. 并发标记不需要 STW并发标记阶段GC 线程和用户线程一起执行。它会扫描整个堆找出存活对象并统计每个 Region 的垃圾比例。比如Region A存活对象 20%垃圾 80% Region B存活对象 30%垃圾 70% Region C存活对象 90%垃圾 10%这些统计信息后面会用于制定回收计划。3. 最终标记STW并发标记过程中用户线程还在运行对象引用关系可能发生变化。所以 G1 也需要一个最终标记阶段修正并发标记期间的变化。这个阶段需要 STW。4. 筛选回收 / 混合回收STW并发标记结束后G1 知道了哪些 Region 垃圾比较多。然后它会根据停顿时间目标选择一部分 Region 进行回收。这一步叫Mixed GC混合回收。为什么叫混合回收因为它回收的不只是新生代 Region还可能包含部分老年代 Region。比如Eden Region Survivor Region Old Region Humongous RegionG1 会选择其中一部分 Region 进行回收。十、G1 使用什么垃圾回收算法G1 整体可以理解为局部复制算法 整体标记-整理思想在具体回收某些 Region 时G1 会把这些 Region 中还活着的对象复制到新的空 Region 中。然后直接清空原来的 Region。比如回收前 Region A [活对象][垃圾][活对象][垃圾] 复制存活对象到 Region B Region B [活对象][活对象] 然后清空 Region A。这样做的好处是不会产生 CMS 那种严重内存碎片因为存活对象被复制到了新的连续空间原 Region 可以整体释放。十一、G1 相比 CMS 的优势1. 解决内存碎片问题CMS 用标记-清除不移动对象所以容易产生碎片。G1 回收 Region 时会复制存活对象然后整体清空旧 Region。所以 G1 可以减少内存碎片。2. 可预测停顿时间CMS 的目标是减少停顿但不太好控制每次停顿时间。G1 可以根据暂停时间目标选择回收哪些 Region。所以 G1 更强调可预测的停顿时间。3. 更适合大堆CMS 在大堆场景下容易出现碎片和 Full GC 问题。G1 把堆划分成 Region可以按区域回收所以更适合大堆内存场景。十二、G1 的缺点G1 也不是完美的。它的缺点主要是实现复杂 维护 Region 信息成本高 需要 Remembered Set 记录跨 Region 引用 小堆场景下不一定比传统回收器更快因为 G1 是按 Region 回收的会遇到一个问题如果只回收某几个 Region那怎么知道其他 Region 有没有引用这里面的对象比如Region A 里的对象 - Region B 里的对象如果现在只回收 Region B那必须知道 Region A 有没有引用 Region B。所以 G1 需要维护额外的数据结构叫Remembered Set记忆集。它用来记录跨 Region 的引用关系。这会带来额外的内存和性能开销。十三、CMS 和 G1 对比对比项CMSG1设计目标低停顿、响应时间优先可预测停顿、兼顾吞吐主要作用老年代回收整个堆回收内存结构新生代 老年代连续划分Region 分区管理回收算法标记-清除复制 标记整理思想是否容易产生碎片容易不容易是否支持暂停时间目标不强支持Full GC 风险较高相对较低适用场景低延迟系统老版本 JVM 常用大堆、低延迟、可预测停顿系统十四、面试时可以这样总结你可以这样说CMS 是一款以响应时间优先为目标的老年代垃圾回收器它采用标记-清除算法主要流程包括初始标记、并发标记、重新标记和并发清理。其中并发标记和并发清理可以和用户线程同时执行因此减少了 STW 时间。但是 CMS 存在内存碎片和浮动垃圾问题当老年代空间不足或发生并发模式失败时会退化为 Serial Old触发长时间 Full GC。然后说 G1G1 是一款面向整个堆的垃圾回收器它把堆划分为多个大小相等的 Region每个 Region 可以动态作为 Eden、Survivor、Old 或 Humongous 区。G1 可以根据用户设置的暂停时间目标优先回收垃圾比例最高、收益最大的 Region。它的回收过程包括初始标记、并发标记、最终标记和混合回收。G1 通过复制存活对象到新的 Region再整体清空旧 Region减少了内存碎片问题适合大堆内存、低延迟、可预测停顿时间的场景。十五、你这份笔记需要改的几个点你整体理解是对的但有几个地方建议修正1. “对象消失”这个说法不太准确你写可能会有些对象的引用关系变化导致漏标(对象消失)建议改成并发标记期间用户线程仍在运行可能修改对象引用关系导致标记结果不准确因此需要重新标记来修正。核心不是“对象消失”而是引用关系变化导致标记结果需要修正。2. “G1 不再分新生代和老年代”不够准确建议改成G1 仍然保留分代思想但物理上不再要求新生代和老年代是连续空间而是由多个 Region 动态组成。也就是说不是没有新生代和老年代 而是新生代和老年代不再是连续的大块内存3. G1 的混合回收不是一次回收所有区域你写得基本对但要强调Mixed GC 不是把所有老年代都回收而是选择一部分收益高的 Old Region 和整个年轻代一起回收。也就是说G1 是“挑着回收”。4. G1 的暂停时间目标不是绝对保证你写STW 的时间不能超过 200ms建议改成G1 会尽量让 STW 时间接近或不超过设定目标但不是绝对保证。因为实际停顿时间受存活对象数量、堆大小、引用关系复杂度等因素影响。一句话总结CMS 是“尽量并发减少停顿”但容易产生碎片和 Full GCG1 是“分区管理优先回收垃圾最多的区域”可以更好地控制停顿时间并减少碎片。