可达性分析算法以GC Roots为起点沿强引用链遍历,标记所有可达对象为存活,不可达对象进入回收队列;该算法天然解决循环引用问题,且只认强引用。

标记阶段:从根对象出发遍历可达对象
标记清除算法的“标记”不是给每个对象加标签,而是通过可达性分析,从一组根对象(如栈帧中的局部变量、静态变量、JNI引用等)开始,递归遍历所有能直接或间接访问到的对象,并将它们在内存中对应的标记位设为“存活”。JVM 通常使用一个额外的位图(bitmap)或对象头中的特定标志位来记录这个状态。比如对象 A 被 GC Roots 直接引用,A 引用 B,B 引用 C,那么 A、B、C 都会被标记;而 D 若不在该引用链上,就不会被标记。
清除阶段:只回收未被标记的对象内存
“清除”不是擦除数据,而是把所有未被标记的对象所占的内存空间,加入空闲链表(free list)或标记为空闲区域,供后续 new 对象时分配使用。这个过程不移动对象位置,所以不会更新引用地址。但会留下大量不连续的小块内存,造成空间碎片。例如,堆中原本有 10MB 连续空间,经过一次标记清除后,可能剩下 4 个分散的 1MB 空闲块,即使总空闲量足够,也无法分配一个 2MB 的大对象。
关键细节:标记与清除都不影响正在运行的程序逻辑
标记清除是“Stop-The-World”操作,即 GC 执行期间所有用户线程暂停。标记过程严格按引用图遍历,确保不会漏标(否则导致存活对象被误删),也不误标(否则浪费空间)。清除过程仅修改内存管理元数据,不调用 finalize() 方法(那是单独的 Finalizer 机制),也不触发对象的清理逻辑。现代 JVM(如 Serial、CMS)的标记清除实现中,还会配合写屏障(write barrier)来处理并发标记时的引用变更问题。
实际应用中的限制与应对
- 无法解决内存碎片问题,因此 CMS 收集器在老年代使用标记清除后,常需配合“空间压缩”(如开启 -XX:+UseCMSCompactAtFullCollection)来定期整理
- 标记过程耗时与存活对象数量成正比,而非全部对象数量,所以适合存活率低的场景(如年轻代一般不用此算法)
- 清除阶段时间与垃圾对象数量成正比,如果大部分对象都是垃圾,清除很快;但如果只有少量垃圾,效率反而偏低
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











