05 - GC 机制
定位: JVM 的自动内存管理器——对象生死的判定者、堆空间的整理者 面试高频度: ⭐⭐⭐⭐⭐
一、GC 在 JVM 体系中的位置
GC (Garbage Collection) 是 Java 实现自动内存管理的核心设施。它与运行时数据区中的堆紧密耦合:
- 上游: 接收 **对象创建过程** 的产出——堆上不断产生新对象
- 核心: 判定哪些对象已经"死亡"(不再被任何引用指向),回收它们占用的内存
- 下游: 影响 **OOM排查方法论** 和 **FullGC频繁排查方法论**——GC 是调优和排查的焦点
GC 解决了 C/C++ 中 malloc/free 手动管理内存的三大痛点:内存泄漏(忘记 free)、野指针(free 后继续使用)、双重释放(free 两次)。
┌─────────────────────────────────────────────────────────────────────┐
│ GC 在 JVM 运行中的位置 │
│ │
│ 类加载 → 对象创建 → 对象使用 → 对象不再被引用 → GC 回收 → 空间复用 │
│ (02) (04) (运行时) (可达性分析) (回收) (堆) │
│ ↓ │
│ GC 介入的时机: │
│ 1. 堆空间不足 (Allocation Failure) │
│ 2. System.gc() (显式触发) │
│ 3. JVM 内部决策 (老年代满、Metaspace不够) │
└─────────────────────────────────────────────────────────────────────┘二、GC 演化时间线
每一代 GC 都是为了解决前一代在特定场景下的痛点:
JDK 1.3 Serial ← 单线程。最简单,适合单核/小堆 (<100MB)
JDK 1.4.2 ParNew ← Serial 的多线程版 Young GC,配合 CMS
JDK 1.4.2 Parallel ← 多线程 Young+Old,追求吞吐量。JDK 8 默认
JDK 1.5 CMS ← 并发老年代 GC。追求低延迟,但有"并发失败"硬伤
JDK 7u4 G1 (实验) ← 分 Region,可预测停顿。JDK 9+ 默认
JDK 11 ZGC ← 彩色指针 + 负载屏障,<10ms 停顿。JDK 21+ 支持分代
JDK 12 Shenandoah ← Brooks 指针并发压缩,<10ms 停顿
│
▼
趋势: 单线程 → 多线程 → 并发 → 几乎全并发
STW 停得越来越短,GC 对应用越来越透明三、子专题导航
| # | 主题 | 面试频率 | 核心内容 |
|---|---|---|---|
| **对象存活判定算法** | 对象存活判定算法 | ⭐⭐⭐⭐⭐ | 引用计数 vs 可达性分析、GC Roots、四种引用类型、finalize() |
| **GC基本算法(标记清除与复制与标记整理)** | GC 基本算法 | ⭐⭐⭐⭐⭐ | 标记-清除、标记-复制、标记-整理,三种算法的对比和选择逻辑 |
| **垃圾收集器详解(Serial到ZGC)** | 垃圾收集器详解 | ⭐⭐⭐⭐⭐ | Serial→ZGC 各收集器架构、CMS缺陷、G1 Region、ZGC 彩色指针 |
| **GC日志分析** | GC 日志分析 | ⭐⭐⭐⭐ | 日志格式解读、关键指标、常见模式排查、GCViewer/GCEasy |
| **GC调优参数速查** | GC 调优参数速查 | ⭐⭐⭐⭐ | 堆/代/收集器参数、G1/ZGC 专属参数、生产配置模板、决策树 |
四、核心考点速记
1. 可达性分析与 GC Roots
当前 JVM 判定对象"活着"的标准:
GC Roots ──→ 直接引用对象 ──→ 间接引用对象 ──→ ……
│ │ │
▼ ▼ ▼
存活 存活 存活
(递归遍历对象图。从 Roots 能到达的 → 活;不能到达的 → 死)GC Roots 集合(面试高频——至少要说出前 4 类):
| 类型 | 内容 | 为什么是 Root |
|---|---|---|
| 虚拟机栈引用 | 局部变量表、参数中引用的对象 | 当前正在执行的方法必须存活的上下文 |
| 静态属性引用 | Class 静态字段引用的对象 | 类的全局状态,GC 不应回收 |
| 常量引用 | 运行时常量池中的引用 | 字符串字面量、final 常量值 |
| JNI 引用 | native 方法栈中的 global/weak 引用 | Java 传入本地代码的对象 |
| 同步锁 | synchronized 持有的对象 | 锁对象被回收 → 锁状态丢失 |
| JVM 内部 | Class 对象、ClassLoader、异常对象 | JVM 正常运行的基础设施 |
2. 三色标记算法
并发标记的核心算法(G1/ZGC/CMS 都基于此):
白色 (White) → 未被 GC 访问 → 可能死亡
灰色 (Gray) → 已访问但未扫描完引用 → 中间态
黑色 (Black) → 已访问且所有引用已扫描 → 确定存活
初始状态: 标记完成:
┌─────┬─────┬─────┐ ┌─────┬─────┬─────┐
│ 白 │ 白 │ 白 │ │ 黑 │ 黑 │ 白 │
│ │ │ │ → 标记后 → │ │ │(死亡)│
│ 白 │ 白 │ 白 │ │ 黑 │ 黑 │ 白 │
└─────┴─────┴─────┘ └─────┴─────┴─────┘并发标记的 Two-Finger Problem : 黑色对象新增指向白色对象的引用 → 白色被误标记为死亡 → 对象丢失。解决方案:增量更新(CMS)或 SATB(G1 的 Snapshot-At-The-Beginning)。
3. 各收集器一句话对比
| 收集器 | 核心思想 | 适用场景 | 最大痛点 |
|---|---|---|---|
| Serial | 单线程 STW | 客户端、小堆 | 多核 CPU 浪费 |
| Parallel | 多线程追求吞吐 | 批处理、离线计算 | 停顿时间长 |
| CMS | 并发标记清除 | Web 服务器 | 并发失败→串行 Full GC |
| G1 | 分区+可预测停顿 | 通用服务 (JDK 9+ 默认) | 大堆 (<64GB) 不如 ZGC |
| ZGC | 彩色指针+全并发 | 超低延迟、超大堆 | 内存开销稍高 |
| Shenandoah | Brooks 指针并发压缩 | 超低延迟 | 吞吐量略低于 ZGC |
五、面试高频追问一览
| 追问 | 关联笔记 | 频次 |
|---|---|---|
| JVM 怎么判断一个对象已死? | **对象存活判定算法** | 极高 |
| 四种引用类型的区别和使用场景? | **对象存活判定算法** | 极高 |
| GC Roots 有哪些? | **对象存活判定算法** | 极高 |
| 三种基本 GC 算法各自的优缺点? | **GC基本算法(标记清除与复制与标记整理)** | 极高 |
| CMS 的优缺点?为什么被 G1 替代? | **垃圾收集器详解(Serial到ZGC)** | 极高 |
| G1 相比 CMS 有哪些改进? | **垃圾收集器详解(Serial到ZGC)** | 极高 |
| ZGC 为什么这么快? | **垃圾收集器详解(Serial到ZGC)** | 高 |
| 怎么选择垃圾收集器? | **垃圾收集器详解(Serial到ZGC)** | 高 |
| 怎么看 GC 日志?Full GC 频繁怎么办? | **GC日志分析** | 高 |
| 你说一下常用的 GC 调优参数? | **GC调优参数速查** | 高 |
📚 相关链接
- **Java堆** — GC 的操作对象,堆的分代结构是理解 GC 的基础
- **对象内存布局(MarkWord与Klass与实例数据)** — MarkWord 中的 GC age 位
- **分配策略(栈上分配与TLAB与大对象)** — GC 前的对象分配策略
- **OOM排查方法论** — GC 失效导致的堆 OOM
- **FullGC频繁排查方法论** — GC 异常导致的性能问题
- ← 返回 **JVM文库设计文档**