JVM 垃圾回收器及其原理与垃圾回收算法
垃圾回收(Garbage Collection, GC)是 JVM 自动管理内存的核心机制。GC 的目标是识别并回收不再使用的对象,释放内存空间。JVM 提供了多种垃圾回收器,每种回收器基于不同的算法,适用于不同的业务场景(吞吐量优先、低延迟优先等)。
一、垃圾回收算法
所有垃圾回收器都基于以下三种基础算法或其组合:
1. 标记-清除(Mark-Sweep)
-
过程:
- 标记:从 GC Roots 出发,遍历所有可达对象,进行标记。
- 清除:遍历堆,回收未被标记的对象内存。
-
优点:实现简单,不需要移动对象。
-
缺点:产生内存碎片,导致后续无法为大对象分配连续空间;效率随对象数量增多而下降。
-
代表:CMS 的“并发清除”阶段。
2. 标记-复制(Mark-Copy)
- 过程:将内存分为两块(如 Eden 和 Survivor),每次只使用一块。当该块满时,将存活对象复制到另一块,然后一次性清除整块。
- 优点:实现简单,无碎片;复制过程高效。
- 缺点:内存利用率低,始终有一块空间空闲。
- 代表:新生代的所有回收器(Serial、ParNew、Parallel Scavenge)。
3. 标记-整理(Mark-Compact)
-
过程:
- 标记存活对象(同标记-清除)。
- 将所有存活对象向一端移动,整理出连续的空闲空间。
-
优点:无内存碎片。
-
缺点:移动对象需要更新引用,停顿时间较长。
-
代表:老年代回收器(Serial Old、Parallel Old、CMS 的 Full GC 阶段)。
4. 分代收集理论
现代 JVM 将堆分为新生代(Young Generation)和老年代(Old Generation),并采用不同的回收算法:
- 新生代:对象“朝生夕死”,适合标记-复制(效率高,碎片少)。
- 老年代:对象存活率高,适合标记-清除或标记-整理(减少移动成本)。
二、垃圾回收器分类与原理
JVM 垃圾回收器按工作区域分为新生代回收器、老年代回收器和统合型回收器(整堆回收)。
| 回收器 | 工作区域 | 算法 | 特点 | 适用场景 |
|---|---|---|---|---|
| Serial | 新生代 | 标记-复制(单线程) | 单线程,Stop-The-World,简单高效 | 客户端模式,单 CPU,小堆内存 |
| Serial Old | 老年代 | 标记-整理(单线程) | 同上 | 同上,或作为 CMS 的备用 Full GC |
| ParNew | 新生代 | 标记-复制(多线程) | Serial 的多线程版本,常与 CMS 配合 | 多 CPU 环境,与 CMS 搭配使用 |
| Parallel Scavenge | 新生代 | 标记-复制(多线程) | 吞吐量优先,可控制吞吐量和最大停顿 | 后台计算,高吞吐量场景 |
| Parallel Old | 老年代 | 标记-整理(多线程) | 吞吐量优先,与 Parallel Scavenge 搭配 | 同上 |
| CMS | 老年代 | 标记-清除(并发) | 低延迟,并发回收,但产生碎片 | 互联网应用,对响应时间敏感 |
| G1 | 整堆 | 分区 + 标记-复制/整理 | 可预测停顿,Region 化,并发 | 大堆内存(4GB+),平衡吞吐与延迟 |
| ZGC | 整堆 | 并发,基于读屏障 | 超低延迟(<10ms),可扩展至 TB 级堆 | 超大堆,极低延迟需求(JDK 11+) |
| Shenandoah | 整堆 | 并发,基于读屏障 | 与 ZGC 类似,低延迟 | OpenJDK 12+,低延迟场景 |
三、主流垃圾回收器详解
1. Serial 与 Serial Old
- Serial:新生代单线程,使用 标记-复制。GC 时暂停所有应用线程(Stop-The-World),适合单 CPU 或小内存环境。
- Serial Old:老年代单线程,使用 标记-整理。作为 CMS 的备选 Full GC 方案。
2. Parallel Scavenge 与 Parallel Old
- Parallel Scavenge:新生代多线程,标记-复制。关注吞吐量(
-XX:GCTimeRatio)和最大停顿时间(-XX:MaxGCPauseMillis)。 - Parallel Old:老年代多线程,标记-整理。与 Parallel Scavenge 搭配,实现高吞吐量回收。
3. CMS(Concurrent Mark Sweep)
-
目标:最小化暂停时间。
-
步骤:
- 初始标记(STW):标记 GC Roots 直接关联的对象。
- 并发标记:与用户线程并发,从 GC Roots 遍历对象图。
- 重新标记(STW):修正并发标记期间发生变化的对象。
- 并发清除:清除未被标记的对象,产生碎片。
-
缺点:CPU 敏感;产生内存碎片;并发模式失败(Concurrent Mode Failure)时会退化为 Serial Old Full GC。
4. G1(Garbage First)
-
目标:可预测的停顿时间(
-XX:MaxGCPauseMillis)。 -
设计:将堆划分为多个大小相等的 Region(1MB~32MB),每个 Region 可扮演 Eden、Survivor、Old 或 Humongous(巨型对象)角色。
-
工作流程:
- 初始标记(STW):标记 GC Roots。
- 并发标记:并发标记整个堆。
- 最终标记(STW):处理 SATB(Snapshot-At-The-Beginning)缓冲区。
- 筛选回收(STW):评估各 Region 的回收价值和成本,选择收益高的 Region 回收(使用标记-复制)。
-
特点:通过 Region 和停顿预测模型,实现可控制的停顿时间;避免全堆扫描。
5. ZGC
- 目标:亚毫秒级停顿(<10ms),支持 TB 级堆。
- 设计:基于 染色指针(Colored Pointers) 和 读屏障。GC 阶段几乎全部并发,只有短暂(<1ms)的 STW 阶段。
- 工作流程:并发标记、并发整理、并发重映射等,利用内存多重映射技术实现对象移动时不暂停应用。
- JDK 版本:JDK 11 实验性,JDK 15 正式可用。
6. Shenandoah
- 目标:与 ZGC 类似,低延迟。
- 设计:基于 转发指针(Brooks Pointer) 和 并发整理,通过读屏障处理对象移动后的引用更新。
- JDK 版本:OpenJDK 12 起正式支持。
四、回收器选择与调优建议
-
客户端模式 / 单 CPU / 小堆(<512MB) :Serial(
-XX:+UseSerialGC)。 -
高吞吐量(后台计算、批处理) :Parallel Scavenge + Parallel Old(
-XX:+UseParallelGC或-XX:+UseParallelOldGC)。 -
低延迟(Web 服务、中间件) :
- 堆内存 < 4GB:CMS(
-XX:+UseConcMarkSweepGC),但注意碎片和并发模式失败。 - 堆内存 4GB ~ 32GB:G1(
-XX:+UseG1GC),设置-XX:MaxGCPauseMillis=100~200。 - 堆内存 > 32GB 且要求极低延迟:ZGC(
-XX:+UseZGC)或 Shenandoah(-XX:+UseShenandoahGC)。
- 堆内存 < 4GB:CMS(
-
混合场景:G1 是 JDK 9+ 的默认回收器,平衡了吞吐量和延迟。
五、总结
| 算法 | 优点 | 缺点 | 代表回收器 |
|---|---|---|---|
| 标记-清除 | 实现简单,不移动对象 | 内存碎片 | CMS(并发清除) |
| 标记-复制 | 高效,无碎片 | 内存利用率低 | 所有新生代回收器 |
| 标记-整理 | 无碎片 | 移动对象开销大 | Serial Old,Parallel Old |
垃圾回收器的选择需结合业务对吞吐量和延迟的要求。现代应用多倾向于使用 G1 或 ZGC 这类可控停顿的回收器,同时配合 GC 日志分析,不断调优参数以达到最佳性能。