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 默认) | 复制 | 并行标记-整理 | 高吞吐 |
| CMS | ParNew(复制) | 并发标记-清除 | 低延迟但有碎片(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(有碎片)
三类 GC 的边界
| 类型 | 回收范围 | 说明 |
|---|---|---|
| Young GC | 只新生代 | STW 短(几十 ms),频率高 |
| Mixed GC(G1) | Young + 部分老年代 Region | 按脏度排序,可预测停顿 |
| Full GC | 整代 | STW 长(秒级),最后手段 |
System.gc() / Survivor 晋升失败
STW 本质与 Safepoint
本质:GC 与线程之间的互斥锁——用短暂停顿换引用图的一致性分析 实现:Safepoint(安全点)- JVM 通知线程跑到最近的 Safepoint 才暂停
- Safepoint = 字节码特定位置:方法调用、循环回跳、异常跳转
- 长方法无 Safepoint 会拖长 STW(HotSpot 通过
-XX:LoopSafepointPollSize自动插入检查) - 排查:
jcmd、JFRVM.print_safepoints 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
🎯 概念
📏 规则
⚠️ 误区
🔍 追问
✨ 口诀
共 0 张卡,点击翻面