JVM GC 算法与 Stop The World

java-concurrency 📚 learning jvm-gc-algorithms · jvm · gc · algorithm · mark-sweep · copy · mark-compact · stw · safepoint · write-barrier

TL;DR(30 秒扫完)

  • 三大算法:标记-清除(有碎片)/ 复制(无碎片但浪费空间)/ 标记-整理(无碎片但移动开销)
  • 新生代用复制(朝生夕死,存活少),老年代用标记-整理(存活多)
  • 权衡三角:吞吐 vs 停顿 vs 空间——不可兼得
  • STW = "GC 与线程的互斥锁",用短暂停顿换状态一致性
  • GC 阶段:Initial Mark(STW)→ Concurrent Mark → Remark(STW)→ Concurrent Sweep
  • CMS 会退化 Full GC(Concurrent Mode Failure),G1/ZGC 尽量避免

关键结论

结论 AGC 器都是三大算法在不同代上的组合(ParallelGC / CMS / G1 / ZGC)
结论 BEden:From:To = 8:1:1,Survivor 只留 10% 逼早熟对象晋升
结论 CSTW 集中在 Initial Mark + Remark 两个阶段,Concurrent 阶段不 STW
结论 DSTW 无法完全消除,只能缩短;ZGC 通过读屏障+多映射压到 <1ms
结论 EGC 流程分两层——对象晋升 + 算法执行,答"完整流程"必须两层都说

完整讲解(费曼四步)

STEP 1 · 概念

GC 算法核心是"标记存活 + 清理死对象"两种操作的不同组合。标记-清除 = 只标记不整理,复制 = 存活复制到另一块空间,标记-整理 = 标记后压缩到一侧。三者权衡:吞吐、停顿、空间不可兼得。

STEP 2 · 大白话

类比:打扫房间(GC):
- 标记-清除:把垃圾围起来(标记),把垃圾扔出去(清理)——省事,但地上留下许多散落的空盒子(碎片)
- 复制:把有用的东西全搬到隔壁房间(复制),把整个房间烧掉(清空)——干净,但浪费隔壁房间
- 标记-整理:把有用的东西按顺序挤到一边(整理),把剩下的一半扔掉——最省空间,但搬东西耗时

STEP 3 · 底层

三大算法对比

算法流程优点缺点使用者
标记-清除GC Roots 标记 → 清理未标简单、无需移动内存碎片CMS 老年代、ZGC 早期
复制堆分两半,存活复制到另一半,清空旧半无碎片浪费一半空间所有分代 GC 的新生代
标记-整理标记存活 → 压缩到一侧无碎片、空间利用率高移动对象要处理所有引用(写屏障)ParallelGC、G1 Region 间、ZGC

GC 器 = 算法组合 + 执行方式

GC 器新生代老年代停顿特征
Serial复制标记-整理单线程,短停顿
ParallelGC(JDK 8 默认)复制并行标记-整理高吞吐
CMSParNew(复制)并发标记-清除低延迟但有碎片(JDK 14 弃用)
G1(JDK 9+ 默认)Region 复制Region 间标记-整理可预测停顿
ZGC / Shenandoah同堆并发标记-整理+读屏障<10ms 停顿

分代结构(HotSpot 默认)

Heap
├── Young Generation (约 1/3)
│   ├── Eden (约 80%)         # 新对象首次分配
│   ├── From Survivor (约 10%) # 上一次存活
│   └── To Survivor (约 10%)   # 本次存活目标
└── Old Generation (约 2/3)    # 长寿 + 大对象
+ Metaspace (JDK 8+ 独立于堆)
晋升 4 条规则:
  • 年龄晋升:GC 次数达 MaxTenuringThreshold(默认 15,动态)
  • 动态年龄判定:Survivor 中同龄对象总和 > 50%,该年龄及更大全部晋升
  • 大对象晋升:超 PretenureSizeThreshold 直接老年代分配
  • 空间分配担保:Survivor 装不下就搬老年代

GC 流程两层(重要!)

第一层:对象晋升
  • Eden 满 → Young GC → 存活复制到 To Survivor → From/To 交换 → 反复几次晋升老年代
  • 第二层:算法执行(以 CMS 为例)
  • Initial Mark(STW):只标 GC Roots,快
  • Concurrent Mark:并发遍历,不 STW
  • Remark(STW):处理引用变动,靠写屏障/Remembered Set 缩短
  • Concurrent Sweep:并发清理,不 STW(有碎片)
Full GC 传统:STW 整代标记 → 标记-整理 → 清理尾部

三类 GC 的边界

类型回收范围说明
Young GC只新生代STW 短(几十 ms),频率高
Mixed GC(G1)Young + 部分老年代 Region按脏度排序,可预测停顿
Full GC整代STW 长(秒级),最后手段
触发 Full GC:老年代满 / Metaspace 满 / System.gc() / Survivor 晋升失败

STW 本质与 Safepoint

本质:GC 与线程之间的互斥锁——用短暂停顿换引用图的一致性分析 实现:Safepoint(安全点)
  • JVM 通知线程跑到最近的 Safepoint 才暂停
  • Safepoint = 字节码特定位置:方法调用、循环回跳、异常跳转
  • 长方法无 Safepoint 会拖长 STW(HotSpot 通过 -XX:LoopSafepointPollSize 自动插入检查)
  • 排查:jcmd VM.print_safepoints、JFR jdk.Safepoint

STW 阶段分布

阶段STW?说明
Initial Mark✅ STW只标 GC Roots,快
Concurrent Mark❌ 并发GC 线程+用户线程并发
Remark✅ STW处理引用变动,靠写屏障缩短
Concurrent Sweep❌ 并发CMS 有,G1/ZGC 用 Concurrent Relocate

Concurrent Mode Failure

并发标记期间老年代空间不足 → 退化 Full GC → 停顿从几十 ms 飙到秒级。这就是"退化回单线程",是线上 GC 事故的常见根因。

STEP 4 · 简化

一句话总结:新生代复制、老年代整理;GC 流程分两层(对象晋升+算法执行);STW 集中在 Initial Mark+Remark;无法消除只能缩短。
记忆口诀:
  • 三大算法:清除 / 复制 / 整理
  • 权衡三角:吞吐 / 停顿 / 空间
  • STW 阶段:Initial Mark + Remark
  • 分代动机:弱分代假说(朝生夕死)

常见误区

"只有 Full GC 才动老年代"
G1 Mixed GC 也会回收部分老年代 Region
"JVM 会自动把对象搬到堆外"
堆外是独立机制,需主动使用
"STW 可以完全消除"
只能缩短(ZGC <1ms),Full GC 仍是秒级
"15 次是 CMS 参数"
MaxTenuringThreshold 是 HotSpot 通用配置
"G1 的 Region 内也会整理"
Region 内不整理(避免移动开销),跨 Region 才复制

延伸追问

为什么新生代用复制不用标记-清除?
朝生夕死,存活少,复制快无碎片;标记-清除新生代虽快但碎片累积影响 Eden 分配
Eden:Survivor = 8:1 是什么原理?
JDK 8 默认 8:1:1,Survivor 只留 ~10% 逼早熟对象晋升;调优 -XX:PretenureSizeThreshold 和 -XX:MaxTenuringThreshold
写屏障是什么?为什么并发 GC 必需?
写屏障是对象引用赋值时插入的额外代码(CardTable 记录跨代指针),让 GC 高效处理并发修改
Remark 为什么需要 STW?如何缩短?
要处理并发标记期间的引用变动,必须 STW 保证一致性;写屏障让变动可追踪,Remark 只处理"脏"引用
如何在生产上观察 GC 流程?
-Xlog:gc* 完整日志、JFR jdk.GCPhasePause 事件、Arthas dashboard、关注 Pause Remark 和 Full GC 频率

速查表

三大算法: 标记-清除 / 复制 / 标记-整理
分代: 新生代复制 / 老年代整理
权衡三角: 吞吐 / 停顿 / 空间
分代结构: Eden 80% + From 10% + To 10% + Old 2/3
晋升 4 规则: 年龄 / 动态年龄 / 大对象 / 空间担保
GC 阶段: Initial Mark (STW) → Concurrent Mark → Remark (STW) → Sweep
三类 GC: Young / Mixed (G1) / Full
STW 阶段: Initial Mark + Remark
CMS 退化: Concurrent Mode Failure

Anki 候选卡片

Q: 三大 GC 算法?
A: 标记-清除 / 复制 / 标记-整理
Q: Eden:From:To 默认比例?
A: 8:1:1
Q: 晋升老年代 4 条规则?
A: 年龄晋升 / 动态年龄判定 / 大对象晋升 / 空间分配担保
Q: 哪些 GC 阶段 STW?
A: Initial Mark + Remark
Q: CMS 退化 Full GC 的原因?
A: 并发标记期间老年代空间不足(Concurrent Mode Failure)

关联题目

  • ⚠️ 《JVM有哪些垃圾回收算法?》— Round 1 Q4, ⭐⭐⭐⭐(术语不准,漏 ZGC)
  • ⚠️ 《Java的堆是如何分代的?为什么分代?》— Round 1 Q5, ⭐⭐⭐(漏 Mixed GC 边界、缺具体结构)
  • ⚠️ 《什么是Stop The World?》— Round 1 Q6, ⭐⭐⭐(缺 Safepoint,术语混乱)
  • ⚠️ 《JVM 中一次完整的 GC 流程是怎样的?》— Round 1 Q7, ⭐⭐(漏算法执行层)

关联知识

三大经典算法:标记-清除/复制/标记-整理;STW 本质是 GC 与线程的互斥锁
打扫房间:标记-清除(省事但留碎片);复制(干净但浪费一半);标记-整理(最省但搬东西耗时)
✦ 记 忆 口 诀 ✦
权衡三角吞吐 vs 停顿 vs 空间不可兼得;STW 只集中在 Initial Mark + Remark
关键可视化
三大 GC 算法对比
flowchart TB
  A["GC 算法"] --> M1["标记-清除<br/>Mark-Sweep<br/>有碎片"]
  A --> M2["复制<br/>Copying<br/>无碎片浪费空间"]
  A --> M3["标记-整理<br/>Mark-Compact<br/>无碎片移动开销"]
  M1 --> U1["CMS 老年代<br/>ZGC 早期"]
  M2 --> U2["所有分代 GC 新生代"]
  M3 --> U3["ParallelGC/G1 Region 间/ZGC"]
HotSpot 分代结构
flowchart TB
  H["Heap"] --> Y["Young Generation<br/>约 1/3"]
  H --> O["Old Generation<br/>约 2/3"]
  H --> M["Metaspace<br/>JDK 8+ 独立于堆"]
  Y --> E["Eden<br/>约 80%<br/>新对象首次分配"]
  Y --> F["From Survivor<br/>约 10%<br/>上一次存活"]
  Y --> T["To Survivor<br/>约 10%<br/>本次存活目标"]
CMS 四阶段(GC 流程两层)
flowchart LR
  P1["1.Initial Mark<br/>STW<br/>只标 GC Roots 快"] --> P2["2.Concurrent Mark<br/>并发遍历<br/>不 STW"]
  P2 --> P3["3.Remark<br/>STW<br/>处理引用变动"]
  P3 --> P4["4.Concurrent Sweep<br/>并发清理<br/>不 STW"]
  P1 -.-> W["写屏障/Remembered Set<br/>缩短 Remark"]
晋升 4 条规则
flowchart TB
  R["对象晋升老年代"] --> R1["年龄晋升<br/>达 MaxTenuringThreshold<br/>默认 15 动态"]
  R --> R2["动态年龄判定<br/>同龄总和>50% 该年龄及更大全部晋升"]
  R --> R3["大对象晋升<br/>超 PretenureSizeThreshold<br/>直接老年代分配"]
  R --> R4["空间分配担保<br/>Survivor 装不下<br/>搬老年代"]
知识关系

⬆️ 前置(Prerequisite)

jvm-memory-model

🔄 延伸(Extends)

jvm-gc-concurrentjvm-gc-select-tune

⚡ 对比(Contrast)

三色标记并发机制— GC 算法讲'怎么做',并发机制讲'怎么并发做'——是 GC 算法的并发实现
🎯 概念 📏 规则 ⚠️ 误区 🔍 追问 ✨ 口诀 共 0 张卡,点击翻面