java垃圾回收以可达性分析法识别存活对象,标记-清除算法易致内存碎片,复制算法适用于存活率低的年轻代且空间利用率约50%,标记-整理算法用于老年代以解决碎片问题但需更新引用。

认识常见的垃圾回收算法,关键不是死记名称和步骤,而是理解每种算法解决什么问题、在什么场景下被采用、以及它带来的代价。Java 的 GC 算法不是孤立存在的,而是围绕“如何高效识别垃圾”和“如何安全释放内存”这两个核心问题逐步演进出来的。
先搞懂“垃圾”是怎么被认定的
所有回收算法的前提,是准确判断一个对象是否真的可以被回收。Java 不用引用计数法(它无法处理循环引用),而是采用可达性分析法:
- 从一组称为GC Roots的对象出发(比如栈里的局部变量、静态字段、常量池中的引用、JNI 引用等);
- 顺着所有引用链向下搜索;
- 凡是能被这条链触达的对象,就是存活的;反之,不可达的就是垃圾。
这个判断过程决定了后续所有回收动作的起点——不是“哪些死了”,而是“哪些还活着”。标记类算法(如标记-清除、标记-整理)都基于此逻辑。
标记-清除:最朴素但有硬伤
这是最基础的两步式算法:
- 标记阶段:遍历堆,对所有存活对象打上标记;
- 清除阶段:把没被标记的对象所占内存直接清空。
优点是实现简单、不移动对象;缺点也很明显:内存碎片化。比如连续释放了几个对象后,空闲空间变成零散小块,后续分配大对象时可能因找不到足够连续空间而提前触发 Full GC。所以它很少单独使用,更多作为其他算法的基础环节。
复制算法:专治“朝生夕灭”的年轻代
针对新生代中绝大多数对象生命周期极短的特点,JVM 把 Eden 区 + 两个 Survivor 区(S0/S1)按 8:1:1 划分,用的就是复制算法:
- 新对象都在 Eden 分配;
- Minor GC 触发时,只把 Eden 和其中一个 Survivor 中仍存活的对象,复制到另一个空的 Survivor;
- 复制完成后,原区域整块清空。
好处是:没有碎片、分配内存只需移动指针(顺序分配)、回收效率高。代价是:需要预留一半空间作“搬运中转”,空间利用率只有 50% 左右——所以它只适合存活率低的区域(如年轻代),不适合老年代。
标记-整理:老年代的稳妥选择
老年代对象存活率高,如果用复制算法,搬来搬去开销太大;如果用标记-清除,又容易碎片化。于是标记-整理应运而生:
- 先标记所有存活对象;
- 再把它们全部往内存一端紧凑排列;
- 最后清理掉边界以外的全部空间。
它解决了碎片问题,也避免了复制算法的空间浪费,但移动对象需要更新所有引用,会增加处理时间。Serial Old 和 Parallel Old 收集器就基于该算法。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











