JVM垃圾回收算法包括标记-清除(碎片多)、复制(高效但耗空间)、标记-整理(碎片少)。分代理论将堆分为年轻代(复制算法)和老年代(标记清除/整理)。现代回收器如CMS(低延迟)、G1(平衡)、ZGC(亚毫秒停顿)针对不同场景优化,选型需考虑吞吐、延迟及堆大小。
一、基础垃圾回收算法
1. 标记-清除(Mark-Sweep)
-
原理:
- 标记阶段:从 GC Roots 出发,遍历所有可达对象并标记。
- 清除阶段:扫描堆内存,回收未被标记的对象。
-
优点:实现简单,无需移动对象。
-
缺点:
- 内存碎片:回收后内存不连续,可能导致大对象分配失败。
- 两次遍历:效率较低,需两次全堆扫描。
-
适用场景:老年代(如 CMS 回收器的初始设计)。
2. 复制算法(Copying)
-
原理:
- 将内存分为两块(如 Eden 区和 Survivor 区)。
- 仅使用其中一块,存活对象复制到另一块,清空原内存。
-
优点:
- 无内存碎片:复制后内存连续。
- 高效回收:仅处理存活对象,适合短生命周期对象。
-
缺点:
- 空间浪费:需预留 50% 空闲内存。
- 复制开销:对象存活率高时效率下降。
-
适用场景:年轻代(如 Serial、ParNew 回收器)。
3. 标记-整理(Mark-Compact)
-
原理:
- 标记阶段:同标记-清除,标记所有可达对象。
- 整理阶段:将存活对象向内存一端移动,清理边界外内存。
-
优点:
- 避免碎片:内存连续,适合长期存活对象。
- 空间利用率高:无需预留额外内存。
-
缺点:
- 移动开销:对象移动增加回收时间。
- 两次遍历:标记和整理均需全堆扫描。
-
适用场景:老年代(如 Serial Old、Parallel Old 回收器)。
4. 引用计数(Reference Counting)
-
原理:
- 每个对象维护一个引用计数器,引用增减时更新计数器。
- 计数器为 0 时立即回收。
-
优点:
- 实时回收:无需等待 GC 触发。
-
缺点:
- 循环引用:无法回收相互引用的对象(如
A→B→A)。 - 性能开销:频繁更新计数器影响效率。
- 循环引用:无法回收相互引用的对象(如
-
适用场景:非 JVM 系统(如 Python、Objective-C)。
二、分代收集算法
基于分代理论,将堆内存划分为年轻代和老年代,针对不同代使用不同算法:
1. 年轻代:复制算法
-
流程:
- 新对象分配至 Eden 区。
- Minor GC 触发时,将 Eden 和 Survivor 区存活对象复制到另一块 Survivor 区。
- 年龄计数器达到阈值(默认 15)的对象晋升至老年代。
-
优化:
- Survivor 区设计:通过两个 Survivor 区(S0/S1)减少内存浪费。
- 分配担保:当 Survivor 区空间不足时,直接晋升至老年代。
2. 老年代:标记-清除或标记-整理
- 标记-清除:用于 CMS 回收器,减少停顿但产生碎片。
- 标记-整理:用于 Parallel Old 回收器,避免碎片但增加停顿时间。
三、现代高级回收算法
1. 并发标记清除(CMS, Concurrent Mark-Sweep)
-
目标:减少老年代回收的停顿时间。
-
流程:
- 初始标记(Stop-The-World):标记 GC Roots 直接关联的对象。
- 并发标记:遍历对象图,标记所有可达对象。
- 重新标记(Stop-The-World):修正并发标记期间变动的引用。
- 并发清除:回收不可达对象。
-
优点:并发执行,降低停顿时间。
-
缺点:
- 内存碎片:需定期 Full GC 整理内存。
- CPU 敏感:并发阶段占用 CPU 资源。
-
适用场景:对延迟敏感的 Web 服务。
2. G1(Garbage-First)
-
目标:平衡吞吐量和延迟,适合大堆内存。
-
核心思想:
- 将堆划分为多个 Region(大小相同,1MB~32MB)。
- 优先回收垃圾最多的 Region(Garbage-First)。
-
流程:
- 初始标记(Stop-The-World):标记 GC Roots 直接关联的对象。
- 并发标记:遍历对象图,标记存活对象。
- 最终标记(Stop-The-World):处理 SATB(Snapshot-At-The-Beginning)记录。
- 筛选回收(Stop-The-World):选择垃圾比例高的 Region 回收。
-
优点:
- 可预测停顿:通过设置
-XX:MaxGCPauseMillis控制目标停顿时间。 - 内存整理:部分 Region 回收时执行整理。
- 可预测停顿:通过设置
-
缺点:内存占用较高(维护 Region 元数据)。
-
适用场景:大内存、对延迟和吞吐均有要求的应用。
3. ZGC(Z Garbage Collector)
-
目标:亚毫秒级停顿,支持 TB 级堆内存。
-
核心技术:
- 染色指针(Colored Pointers):在指针中存储元数据(如标记、重定位信息)。
- 读屏障(Read Barrier):拦截指针访问,处理并发回收时的对象移动。
-
流程:
- 并发标记:遍历对象图,标记存活对象。
- 并发预备重分配:确定需回收的 Region。
- 并发重分配:移动对象并更新引用。
- 并发重映射:修正旧地址的引用。
-
优点:
- 极低停顿:停顿时间不随堆大小增长。
- 全堆并发:几乎所有阶段均可并发执行。
-
缺点:JDK 15+ 才正式生产可用,兼容性要求高。
-
适用场景:超大规模内存、严格低延迟的应用(如金融交易系统)。
四、算法对比与选型建议
| 算法/回收器 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Serial | 简单,单线程低开销 | 停顿时间长 | 客户端应用、小内存 |
| Parallel | 高吞吐量 | 停顿时间较长 | 后端批处理、计算密集型 |
| CMS | 低延迟 | 内存碎片、CPU 敏感 | Web 服务、响应优先 |
| G1 | 平衡吞吐和延迟,大堆友好 | 内存占用较高 | 通用服务器应用 |
| ZGC | 亚毫秒级停顿,超大堆支持 | 需最新 JDK,兼容性限制 | 超低延迟、TB 级内存 |
五、总结
-
基础算法:
- 标记-清除简单但碎片多,复制算法高效但浪费空间,标记-整理平衡碎片与效率。
-
分代理论:
- 年轻代用复制算法快速回收,老年代用标记-清除/整理处理长生命周期对象。
-
现代回收器:
- CMS 以低延迟为目标,G1 平衡吞吐与延迟,ZGC 追求极致低停顿。
-
选型关键:
- 根据应用场景(吞吐、延迟、堆大小)和 JDK 版本选择合适的回收器。