可达性分析算法以gc roots为起点遍历引用链判定对象存活,gc roots包括虚拟机栈局部变量、方法区静态字段与常量、jni引用、被锁对象及运行中线程;只要对象能通过任意引用链连至任一gc root即存活,否则标记为可回收。

GC 判定对象是否可达,不看变量有没有被赋值为 null,也不看对象本身是不是“空”,只看它能不能从一组确定的起点——GC Roots——出发,沿着引用链被访问到。能走到,就存活;走不到,就标记为可回收。
GC Roots 是哪些对象?
它们是程序中“绝对不能被回收”的引用源头,包括:
- 虚拟机栈中正在使用的方法里的局部变量(比如
String s = new String("abc")中的s) - 方法区中类的静态变量(如
public static List<user> cache = new ArrayList()</user>) - 方法区中常量池里的常量引用(如
public static final User DEFAULT_USER = new User()) - 本地方法栈中 JNI 调用所持有的 Java 对象
- 正在被 synchronized 锁住的对象(HotSpot 实现中会纳入)
引用类型决定“可达”的松紧程度
不是所有引用都一样牢靠。Java 中四类引用对 GC 的影响差异明显:
-
强引用:最常见,如
User u = new User()。只要它存在,对象绝不会被回收,哪怕内存溢出 -
软引用:用
SoftReference包装。内存紧张时才回收,适合缓存场景 -
弱引用:用
WeakReference包装。下一次 GC 就会被清理,常用于避免内存泄漏的监听器、缓存映射表 -
虚引用:仅用于跟踪对象被回收的时机,无法通过它获取对象实例,需配合
ReferenceQueue
可达性分析是怎么跑起来的?
GC 不是逐个检查每个对象有没有引用,而是从所有 GC Roots 同时出发,像撒网一样遍历整个引用图:
- 采用深度优先或广度优先方式,顺着每一条引用路径往下走
- 所有能走到的对象打上“存活”标记;没被打上的,就是不可达对象
- 这个过程天然解决循环引用问题——A 引用 B、B 引用 A,但两者都没连上任何 GC Root,照样被回收
对象还有没有“自救”机会?
历史上有 finalize() 方法允许对象在被回收前做最后操作,甚至重新与 GC Roots 建立强引用实现“复活”。但该机制已被标记为废弃:
- JDK 9 起弃用,JDK 18 彻底移除
- 执行时机不确定、性能差、易引发死锁
- 现代替代方案是
Cleaner或PhantomReference+ 引用队列











