标记-清除与标记-整理的核心分歧在于空闲内存处理方式:前者保留碎片、不移动对象、无需更新引用;后者通过滑动迁移消除碎片,但必须更新所有引用、复制数据并承受硬件适配代价。

Java 的垃圾回收(GC)底层确实广泛采用标记-清除(Mark-Sweep)思想,但需注意:现代 JVM(如 HotSpot)**并不直接使用原始的、朴素的标记-清除算法作为主力回收器**,而是将其核心逻辑(标记 + 清除)作为基础组件,融合进更高效的混合策略中。理解它,是读懂 GC 机制的起点。
标记阶段:如何识别“活着”的对象?
JVM 从一组称为“GC Roots”的对象出发(如栈帧中的局部变量、静态字段、本地方法栈引用等),沿着所有可达引用链进行深度优先或广度优先遍历。被遍历到的对象被打上“存活”标记(例如在对象头中设置一个标记位)。未被标记的对象,即为“不可达”,判定为垃圾。
- GC Roots 是起点,不是全部对象;只有从 Roots 出发能到达的对象才算活的
- 标记过程必须暂停所有用户线程(Stop-The-World),保证引用关系不变化
- 标记本身不移动对象,只改变元数据状态,开销相对可控
清除阶段:怎么回收内存空间?
标记完成后,JVM 扫描整个堆(或目标区域,如老年代),把所有未被标记的对象所占内存空间释放掉,并加入空闲内存列表(free list)供后续分配使用。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 清除操作不整理内存,会导致堆中出现大量不连续的碎片空间
- 碎片可能让后续大对象无法分配,即使总空闲空间足够(这就是“空间浪费”问题)
- 原始 Mark-Sweep 因此不适合频繁分配大对象的场景,这也是它被优化的根本原因
为什么 Java 不直接用纯 Mark-Sweep?
因为纯标记-清除有两个硬伤:碎片化严重、分配效率低。所以 HotSpot JVM 将其演进为更实用的组合:
- Serial Old / CMS(已废弃):基于标记-清除,但 CMS 通过并发标记尝试减少 STW 时间,仍受碎片困扰
- 标记-整理(Mark-Compact):在清除后增加“整理”步骤,把存活对象向一端移动,消除碎片(如 Serial Old 在 Full GC 时可选)
- 分代收集 + 复制算法:年轻代用复制算法(如 Eden + Survivor),高效处理大量朝生暮死对象;老年代才更依赖标记逻辑,但通常搭配整理或增量回收
怎么验证和观察标记-清除行为?
虽然看不到“内部标记位”,但可通过 JVM 参数和日志间接确认其逻辑痕迹:
- 加 -XX:+PrintGCDetails -Xlog:gc*(JDK 9+)启动程序,触发 Full GC 后查看日志中的 “Marking”、“Sweeping” 或 “Phase X” 阶段描述
- 使用 jstat -gc
观察老年代(Old Gen)的容量变化和 GC 次数,多次 Full GC 后若老年代碎片增多(可用空间下降快于回收量),就反映清除后未整理的特征 - 配合 -XX:+UseSerialGC 强制使用 Serial 收集器,其老年代默认用标记-整理,但开启 -XX:-UseCompressedOops 或特殊配置下可更贴近经典行为
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










