主题
概念卡片:JVM 垃圾回收 GC
一句话机制
GC 分两步:先「判定对象已死」(可达性分析,从 GC Roots 出发找引用链),再「回收」——按分代选算法(新生代标记-复制、老年代标记-整理),用不同收集器(Serial/ParNew/Parallel/CMS/G1)在吞吐量与停顿时间之间取舍。 这是 JVM 调优的核心前置。
判定对象已死
| 方法 | 原理 | 问题 |
|---|---|---|
| 引用计数法 | 每处引用 +1,失效 -1,归 0 即死 | 循环引用无法回收(Obj1↔Obj2 互相引用) |
| 可达性分析 | 从 GC Roots 沿引用链遍历,不可达即死 | JVM 采用(解决循环引用) |
GC Roots 四类:栈帧局部变量表引用的对象 · 本地方法栈 JNI 引用 · 静态属性引用 · 常量引用。
可达性分析要求「一致性快照」,最简单是 Stop The World;若与用户线程并发,用增量更新(CMS)或原始快照 SATB(G1)记录变化。
四种引用类型
| 引用 | 回收时机 |
|---|---|
| 强引用 | 永不回收(不置 null) |
| 软引用 SoftReference | 内存不足才回收(可做缓存) |
| 弱引用 WeakReference | 一触发 GC 就回收(ThreadLocal 用) |
| 虚引用 PhantomReference | 回收时收系统通知(直接内存管理) |
四大回收算法
| 算法 | 适用 | 优点 | 缺点 |
|---|---|---|---|
| 标记-清除 | 老年代(CMS) | 不移动对象、停顿短 | 内存碎片 |
| 标记-复制 | 新生代 | 无碎片、快 | 空间利用率低、对象存活率高时效率低 |
| 标记-整理 | 老年代 | 无碎片 | 移动对象、必须 STW |
| 分代 | 整体策略 | 各代用最优算法 | — |
新生代复制算法的改良:不 1:1 划分,而是 Eden:S0:S1 = 8:1:1,只复制存活对象(因为绝大多数对象熬不过第一轮 GC),装不下的直接进老年代。
收集器全景
新生代(复制算法) 老年代
Serial ──────────────── Serial Old(标记-整理)
ParNew ──┐
Parallel Scavenge ── Parallel Old
└── CMS(标记-清除,可配 ParNew)
G1(整个堆,Region 布局)| 收集器 | 线程 | 目标 | 特点 |
|---|---|---|---|
| Serial | 单线程 | — | Client 模式,STW |
| ParNew | 多线程 | — | 唯一能与 CMS 搭配的新生代收集器 |
| Parallel Scavenge | 多线程 | 吞吐量优先 | -XX:MaxGCPauseMillis/GCRatio + 自适应 |
| CMS | 并发 | 最短 STW | 四步 + 并发失败/碎片问题 |
| G1 | 并发 | 可预测停顿 | Region + 记忆集,JDK9 起默认 |
CMS 四步与三大缺点
初始标记(STW) → 并发标记(增量更新) → 重新标记(STW) → 并发清除| 缺点 | 说明 | 解法 |
|---|---|---|
| CPU 敏感 | 并发阶段占 CPU,应用变慢 | — |
| 并发失败 CMF | 并发期间用户线程耗尽预留内存 | 调高开始 GC 的阈值 |
| 晋升失败 + 碎片 | 标记-清除产生碎片,放不下晋升对象 | CMSFullGCsBeforeCompaction 定时整理 |
G1 核心(JDK9 起默认)
- Region 布局:堆分成大小相同的 Region(默认 2048 个),每个 Region 可扮演 Eden/Survivor/Old/Humongous(大对象)四种角色
- 可预测停顿:用户指定期望停顿(单次 STW 默认 ≤200ms),G1 维护「回收收益 vs 成本」优先级列表,只回收收益最大的 Region
- 记忆集 + 卡表:解决跨 Region 引用,避免全堆扫描(每个 Region 记录谁指向自己)
- SATB 原始快照:并发标记用原始快照保证一致性
- 四步:初始标记 → 并发标记 → 最终标记(STW)→ 筛选回收(STW,并行复制)
- Mixed GC:G1 特有,回收新生代 + 部分老年代
GC 划分与触发
| GC 类型 | 回收范围 | 触发 |
|---|---|---|
| Young GC | 新生代 | Eden 满 |
| Old GC | 老年代 | 仅 CMS 有 |
| Mixed GC | 新生代 + 部分老年代 | 仅 G1 有 |
| Full GC | 整堆 + 方法区 | 老年代不足/空间分配担保失败/System.gc()/方法区不足 |
常见误解(避坑)
- ❌ "引用计数是 JVM 的判定方式"。→ JVM 用可达性分析,引用计数有循环引用问题。
- ❌ "G1 也是分新生代老年代两块的固定布局"。→ G1 是 Region 化的,Region 角色动态变化,不是固定两块。
- ❌ "CMS 没有 STW"。→ CMS 的初始标记和重新标记都要 STW,只是把耗时最长的并发标记/清除放到了并发阶段。
- ❌ "对象没被引用就一定立即回收"。→ 还要经过 finalize()(可自救一次),且软引用对象在内存充足时不会被回收。
关联
- 原始资料:3.GC · 4.垃圾回收器
- 总览:JVM技术栈总览
- 相关卡:概念卡片:JVM内存区域与对象模型(堆分代)· 概念卡片:JVM调优与问题排查
- 域地图:A00-百科/Java后端/Java后端