GC Roots有四类:虚拟机栈中局部变量引用的对象、方法区静态字段引用的对象、方法区常量池引用的对象、本地方法栈中JNI全局引用指向的对象。

哪些对象能当 GC Roots?不是“谁重要”,而是“谁绝对不能被回收”
GC Roots 不是凭空选出来的,而是 JVM 从设计上就认定“此刻必须存活”的几类对象。它们构成可达性分析的起点——只要一个对象能通过引用链连到任意一个 GC Root,它就不会被回收。
真正起作用的只有四类(JDK 8–17 通用,JDK 21+ 无本质变化):
-
虚拟机栈中正在执行的方法的局部变量表里引用的对象(比如MyClass obj = new MyClass()中的obj) -
方法区中类的静态字段引用的对象(如public static List<string> cache = new ArrayList()</string>中的cache实例) -
方法区常量池中引用的对象(典型是字符串常量池里的"hello",但注意:JDK 7+ 后字符串常量池移到堆中,它本身仍是 GC Root) -
本地方法栈中 JNI 全局引用(jobject类型的GlobalRef)指向的对象;局部 JNI 引用(LocalRef)不算,它们随 JNI 方法退出自动失效
别被“系统类加载器”“活跃线程对象”等说法带偏——它们本身只是上述四类的载体。比如“线程对象”能当 GC Root,是因为它存于虚拟机栈或被静态引用;“Bootstrap 类加载器”能当 GC Root,是因为它由 JVM 内部硬编码持有,属于方法区静态结构的一部分。
为什么 obj9 和 obj10 会被回收?即使它们互相引用
这是可达性分析最常被误解的一点:循环引用不等于可达。GC 不看对象之间怎么连,只看能不能从 GC Roots “摸”过去。
立即学习“Java免费学习笔记(深入)”;
假设 obj9 和 obj10 只有彼此的引用,且没有被栈变量、静态字段、常量或 JNI 引用任何一方持有,那它们到 GC Roots 的路径就是断的。
常见错误现象:
- 缓存 Map 里存了对象,但 key 是局部变量临时构造的,方法一结束 key 就不可达 → 整个 entry 可能被回收(尤其配合 WeakHashMap 时更明显)
- JNI 回调中把 Java 对象存成 C 全局指针,但忘了调
NewGlobalRef→ 下次 GC 就可能回收该对象,再访问触发JNIEnv::GetObjectClass: bad object - 静态集合里 add 了对象,但忘了 remove,导致本该释放的对象一直被静态字段拖着 —— 这是内存泄漏高发场景
方法区里的“常量引用”到底指什么?String.intern() 算不算
“常量引用”特指运行时常量池(Runtime Constant Pool)中已解析的 CONSTANT_String_info、CONSTANT_Class_info 等项所指向的 Java 对象。它不等于“所有字符串”,也不等于“所有 final 字段”。
关键区分:
-
"abc"字面量:编译期进入常量池,对应堆中字符串对象是 GC Root -
new String("abc").intern():如果常量池中尚无该字符串,则将堆中对象注册进常量池 → 此时它成为 GC Root;否则返回已有引用,不新增 -
public static final String X = "abc":字段本身是 GC Root(静态属性),其值也是(常量池引用),双重保险 -
public static final String Y = new String("def"):Y 是 GC Root(静态字段),但 new 出来的对象不是 —— 它没进常量池,只靠静态字段引用维系
所以 intern() 不是“让对象变 GC Root 的魔法”,而是“尝试把它塞进常量池这个 GC Root 集合”。是否成功,取决于常量池里有没有它。
本地方法栈中的 JNI 引用,为什么最容易出问题
JNI 是 GC Roots 里最危险的一环:JVM 看得见引用,但看不见你 C/C++ 代码里怎么用它。一旦管理失当,要么提前回收(crash),要么永久驻留(leak)。
实操建议:
- 绝不用
LocalRef跨 JNI 方法边界保存 —— 它在env返回后即失效 - 要用全局引用,必须显式调
env->NewGlobalRef(obj),且配对env->DeleteGlobalRef(ref) - 避免在 native 层长期持有
jobject而不转为weak global ref;若需弱引用,用NewWeakGlobalRef+IsSameObject检查是否还存活 - GC 日志里看到
Full GC (Ergonomics)频繁触发,且jni weak global refs数量持续上涨,基本可判定 JNI 引用未释放
GC Roots 的枚举本身开销极小,但它的“有效性”完全依赖程序员对这四类边界的清醒认知——尤其是 JNI 和静态集合这两块,写错一行,排查三天。









