JVM垃圾回收器CMS与G1

wen java案例 1

本文目录导读:

JVM垃圾回收器CMS与G1

  1. 核心区别一览
  2. CMS详解(已淘汰,但思想重要)
  3. G1详解(现代默认)
  4. 总结对比:何时用哪个?

这是一个非常经典且重要的JVM面试题,CMS和G1都是低延迟垃圾回收器,但它们的核心设计思想和适用场景有很大不同。

我将从设计目标、工作步骤、核心特点、优缺点、适用场景几个维度进行对比分析。


核心区别一览

特性 CMS (Concurrent Mark Sweep) G1 (Garbage First)
设计目标 最短停顿时间,追求低延迟 可预测的停顿时间,兼顾吞吐量与延迟
堆内存结构 分代(新生代 + 老年代),物理隔离 分区(Region),逻辑分代,物理不分代
算法 标记-清除 标记-整理 + 复制
主要步骤 初始标记 -> 并发标记 -> 重新标记 -> 并发清除 初始标记 -> 并发标记 -> 最终标记 -> 筛选回收
GC触发方式 老年代占用达到阈值(-XX:CMSInitiatingOccupancyFraction) 预估GC停顿时间,计算回收收益最高的Region
碎片问题 (产生内存碎片,可能导致Full GC) (基于Region复制/整理,不产生碎片)
Full GC 发生碎片或晋升失败时,退化为Serial Old(单线程,极慢) 发生并发标记失败或晋升失败时,退化为Serial Old(单线程)
Java版本 JDK 9开始标记为废弃,JDK 14正式移除 JDK 7引入,JDK 9起成为默认GC

CMS详解(已淘汰,但思想重要)

工作流程(4步)

  • 初始标记(STW,很短):标记GC Roots直接关联的对象,以及年轻代指向老年代的引用。
  • 并发标记(无STW):从GC Roots开始遍历整个老年代,标记所有存活对象。耗时最长,与应用线程并发执行。
  • 重新标记(STW,比初始标记长):修正并发标记期间因用户线程继续运行而产生变动的对象标记记录。
  • 并发清除(无STW):清除未被标记的对象,回收内存。

优点

  • 并发标记 & 并发清除:真正做到了低延迟,停顿时间极短。
  • 对老年代回收效率高(在碎片不严重时)。

痛点

  • 内存碎片:标记-清除算法必然导致碎片,当碎片太多,无法为大对象分配连续空间时,触发Full GC(Serial Old,单线程,非常慢)。
  • CPU敏感:并发阶段占用CPU资源,在CPU核心少的环境下会拖慢应用。
  • 浮动垃圾:并发标记阶段产生的垃圾无法在本次回收,只能等下次,因此需要预留空间给并发标记线程执行,不能等到堆满才触发。
  • 并发模式失败(Concurrent Mode Failure):如果并发标记期间,预留空间不够,触发Full GC。

G1详解(现代默认)

G1的核心思想是 “把堆分成多个独立的小块(Region),每次只回收收益最高(垃圾最多)的那一批”

堆内存结构(Region)

  • Region:堆被分成约2048个大小相等(1MB-32MB)的Region。
  • Eden / Survivor / Old / Humongous(巨型对象):这些逻辑分代对应不同Region,但Region是动态分配的,某个Region之前是老年代,回收后可变为年轻代。
  • Humongous Region:专门存放大小超过Region 50%的对象,防止大对象在正常Region间移动。

工作流程(5步)

  1. 初始标记(STW,很短):标记GC Roots直接关联的对象。同时完成了Young GC(年轻代回收)
  2. 并发标记(无STW):从GC Roots开始遍历整个堆(包括老年代),标记存活对象。
  3. 最终标记(STW,略长):处理并发标记短暂遗漏的引用变更(SATB,Snapshot-At-The-Beginning算法)。
  4. 筛选回收(STW,可预测)核心步骤,G1会统计所有Region的回收收益(回收垃圾量 / 回收耗时),然后选择一批收益最高的Region进行回收,回收时使用复制算法,将存活对象压缩到另一个Region,彻底消除碎片。
  5. 可选:并发清除(无STW):回收完全没对象的Region。

关键特性

  • 停顿时间模型:通过 -XX:MaxGCPauseMillis(默认200ms)指定目标停顿时间,G1会动态调整年轻代大小、Region回收数量等来尽量满足此目标。
  • 无碎片:因为回收时用了复制/整理算法。
  • 可预测停顿:通过筛选回收,每次STW时间可控。

优点

  • 低延迟:停顿时间可控,且通常远低于CMS的Full GC风险。
  • 高吞吐量:在停顿时间目标下,尽量多回收内存。
  • 自动调优:不需要额外设置太多参数(如CMS的-XX:CMSInitiatingOccupancyFraction)。

缺点

  • 内存占用略高:需要额外的Remembered Set(记录跨Region引用)数据结构,约5%-10%的堆内存。
  • 响应时间不如ZGC/Shenandoah:虽然优于CMS,但在极低延迟(<10ms)场景下不如ZGC。

总结对比:何时用哪个?

  • CMS(历史选择)

    • 适合JDK 8及以前,对停顿时间敏感,且堆内存不大(<4GB~6GB) 的场景。
    • 注意:如果堆很大(>6GB),CMS的碎片和Full GC风险会指数级上升。
  • G1(现代选择)

    • JDK 9及以上的默认选择。
    • 适合堆内存较大(>6GB),且需要可预测、较低停顿时间(如100ms-500ms) 的场景。
    • 适合需要消除内存碎片,避免长时间Full GC的应用。
  • 超越G1

    • 如果延迟要求极高(<10ms),或者堆内存极大(>100GB),应优先考虑 ZGC(JDK 11+)Shenandoah(JDK 12+)

CMS是追求低延迟的“先驱”,但受困于内存碎片和Full GC风险;G1是更均衡的“后继者”,通过分区回收和停顿预测,在延迟与吞吐量之间取得了更好的平衡,并已成为现代Java应用的主流选择。

抱歉,评论功能暂时关闭!