垃圾回收算法有哪些?能简单介绍一下各自的核心逻辑吗?
垃圾回收算法是 JVM 回收垃圾对象的"核心策略",核心有 4 类基础算法 + 1 类组合策略(分代回收),它们的设计都是围绕"高效识别垃圾、回收内存、减少影响"展开的。
一、核心基础算法(4 类)
1. 复制算法(Copying GC)
- 核心原理:把内存分成两块大小相等的区域(A 区、B 区),只在 A 区分配对象;GC 时,找出 A 区所有存活对象,复制到 B 区并紧凑排列,然后清空 A 区,下次分配切换到 A 区(循环往复)。
- 优点:回收速度快(只复制存活对象,不用扫描垃圾);回收后无内存碎片(对象紧凑排列)。
- 缺点:内存利用率低(只能用一半内存);存活对象多的时候,复制成本高(要拷贝大量对象)。
- 适用场景:年轻代(比如 Eden + Survivor 区)------ 年轻代对象"朝生夕死",存活比例极低,复制成本低,刚好适配算法优势。
2. 标记-清除算法(Mark-Sweep)
- 核心原理:分两步走:① 标记:从 GC Roots 出发,遍历所有对象,给存活对象打标记;② 清除:遍历整个内存,把没打标记的垃圾对象回收,释放内存。
- 优点:内存利用率高(不用拆分内存,全量使用);不用移动对象(减少 CPU 开销)。
- 缺点:回收后产生大量内存碎片(垃圾对象零散分布,释放后留下零散空闲块);效率受对象总数影响(要遍历所有对象,不管存活还是垃圾)。
- 适用场景:老年代日常回收------ 老年代对象存活比例高,标记存活对象比扫描垃圾快,且不用移动对象,适合频繁触发的回收。
3. 标记-整理算法(Mark-Compact)
- 核心原理:在"标记-清除"基础上多一步"整理":① 标记(和标记-清除一致);② 整理:把所有存活对象向内存一端移动,紧凑排列;③ 清除:清空内存另一端的垃圾对象,释放连续空闲空间。
- 优点:无内存碎片(对象紧凑排列);内存利用率高(全量使用内存)。
- 缺点:效率比标记-清除低(多了"移动对象"的步骤,还要更新对象引用地址);STW 停顿时间更长。
- 适用场景:老年代碎片较多时------ 比如多次标记-清除后,内存碎片积累,需要用它整理内存,保证大对象能顺利分配。
4. 标记- sweep - compact 混合算法(Mark-Sweep-Compact)
- 核心原理:不是独立算法,而是"标记-清除"和"标记-整理"的结合:日常用标记-清除保证回收效率,当内存碎片达到阈值(比如大对象分配失败),触发一次标记-整理清理碎片。
- 优点:兼顾效率和内存利用率(日常快,碎片多了再整理)。
- 缺点:逻辑更复杂(要判断碎片阈值)。
- 适用场景:老年代主流策略------ 比如 CMS 回收器的"并发标记清除"+ 定期"标记整理",就是这种混合思路。
二、组合策略:分代回收算法(Generational GC)
- 核心原理:不是独立算法,而是"根据对象生命周期划分内存区域,适配不同基础算法"的策略------ 年轻代用复制算法,老年代用标记-清除/标记-整理算法(混合)。
- 设计依据:对象生命周期有明显差异(大部分对象短寿,少数对象长寿),针对性选择算法能最大化效率。
- 优点:兼顾年轻代"快速回收"和老年代"高内存利用率",是目前最成熟的 GC 策略。
- 适用场景:绝大多数 JVM 场景(比如 JDK 8 的 ParNew+CMS、Parallel Scavenge+Parallel Old,都是分代回收)。
三、补充:不分代算法的核心思路(简单提一句)
现在还有打破分代模型的算法(比如 G1、ZGC 用的"区域化回收"),核心是把堆分成多个小 Region,每个 Region 独立回收,按需触发"复制"或"整理",本质是"基础算法的区域化应用",目的是减少 STW 停顿,适配大堆内存(比如 TB 级)。
总结
核心可以概括为:基础算法是"工具",分代回收是"策略"------ 复制算法快、无碎片但费内存(适合年轻代);标记-清除高效但有碎片(适合老年代日常);标记-整理无碎片但慢(适合老年代碎片清理);分代回收则是把这些工具组合起来,适配不同区域的对象特性,实现整体 GC 最优。