java垃圾回收采用可达性分析算法判断对象存活,以gc roots为起点沿强引用链遍历,不可达对象被标记为可回收,天然解决循环引用问题。

Java 垃圾回收通过可达性分析算法判断对象是否存活,核心是“从一组确定的起点出发,看能否触达某个对象”。只要对象能被 GC Roots 通过强引用链访问到,就视为存活;否则标记为可回收。这个过程不依赖引用计数,天然规避循环引用问题。
可达性分析的基本逻辑
算法本质是一次图遍历:把堆中所有对象看作节点,引用关系看作有向边,GC Roots 是起始节点。JVM 从这些根出发,沿强引用方向(如 obj.field、array[i])广度或深度搜索,所有能走到的对象打上“存活”标记。
- 不可达 ≠ 立即回收:未被标记的对象进入待回收队列,现代 JVM(如 ZGC、Shenandoah)通常直接回收,不再走 finalize 自救流程
- 只认强引用:软引用、弱引用、虚引用不构成有效路径,不影响对象存活判定
- 循环引用无影响:A 指向 B、B 指向 A,但两者都不连向 GC Roots → 全部不可达 → 全部可回收
GC Roots 的七类典型来源
GC Roots 必须是 JVM 能明确保证“此刻一定活跃”的对象,不能是临时或可能失效的引用。常见类型包括:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
虚拟机栈中的局部变量:方法内定义的引用,如
Object obj = new Object()中的obj -
静态字段引用的对象:类加载时初始化的
public static Map cache = new HashMap() -
常量池中的字符串等常量:如
private static final String MSG = "ok" -
JNI 全局引用:Native 代码调用
NewGlobalRef创建的持久引用 - 正在执行的 Java 线程本身:线程对象及其栈帧都算作 Roots
-
synchronized 锁住的对象:如
synchronized(obj) { ... }中的obj在同步块执行期间是 Root -
系统类加载器、Class 对象、基本类型 Class 实例:如
String.class、int.class
实际判断时的关键细节
是否可达,取决于引用链在 GC 发生时刻的实时状态,而非代码写法本身:
- 方法执行结束 → 栈帧弹出 → 局部变量失效 → 它们引用的对象失去 Root 支撑
- 静态变量设为
null→ 断开静态引用 → 原来依赖它的整个子图可能集体不可达 - JNI 局部引用在 Native 方法返回后自动释放,不构成稳定 Root;只有全局引用才算
- final 字段初始化完成后不可变,但若它指向的对象本身被置 null,仍会切断路径
为什么不用引用计数?
引用计数法实现简单,但无法处理对象间互相持有引用却脱离业务逻辑的情况。例如:
A a = new A(); B b = new B(); a.ref = b; b.ref = a; a = null; b = null;
此时 a、b 在业务上已不可访问,但引用计数均不为 0。Java 放弃该方案,转而用可达性分析——只要没路径连回 GC Roots,哪怕引用计数非零也照收不误。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










