GC Roots是虚拟机栈、本地方法栈、方法区静态变量与常量、同步锁对象及JVM内部关键对象等强引用起点;通过从这些根出发遍历引用链标记可达对象,未标记者即不可达并可回收。

Java 的垃圾回收(GC)通过从 GC Roots 出发,沿引用链向下遍历对象图,标记所有可达对象;未被标记的对象即为不可达对象,可被回收。理解 GC Roots 是掌握对象存活判断机制的关键。
哪些对象可以作为 GC Roots?
GC Roots 并非 Java 中的某个类或变量,而是一组“始终被视为活跃”的引用起点,包括:
- 虚拟机栈中引用的对象:每个线程栈帧里的局部变量、方法参数等持有的对象引用;
- 本地方法栈中 JNI 引用的对象:通过 JNI 调用 native 代码时创建的全局/局部引用;
- 方法区中静态变量引用的对象:如 public static Object instance = new Object(); 中的 instance 指向的对象;
- 方法区中常量引用的对象:如字符串常量池中的字符串对象(JDK 7+ 后常量池移到堆中,但逻辑上仍属 GC Roots);
- 正在被同步锁持有的对象:synchronized 块或方法锁住的对象(JVM 内部实现层面会将其视为根);
- JVM 内部关键对象:如基本类型 Class 对象、系统类加载器、重要异常对象(OutOfMemoryError)等。
追踪过程:如何判定一个对象不可达?
以 CMS、G1 或 ZGC 等现代收集器为例,其标记阶段本质是图的遍历(通常为广度优先):
- 从所有 GC Roots 开始,将它们直接引用的对象加入待标记队列;
- 逐个取出队列中的对象,扫描其成员变量(即引用字段),将新发现的、尚未标记的对象加入队列;
- 重复该过程直到队列为空;
- 堆中所有未被标记的对象,即为不可达对象,进入回收候选集。
注意:即使对象之间相互引用(如 A → B 且 B → A),只要二者都不被任何 GC Root 直接或间接引用,整个环都会被判定为不可达。
立即学习“Java免费学习笔记(深入)”;
常见误判场景与调试建议
开发中容易因隐式强引用导致对象“意外存活”,例如:
- 静态集合类持有对象:如 static List<Object> cache = new ArrayList<>(); 添加后未清理,会使对象长期无法回收;
- ThreadLocal 残留引用:线程结束但 ThreadLocalMap 未清理,尤其在线程池复用场景下易造成内存泄漏;
- 内部类持外部类引用:非静态内部类默认持有外部类 this,若内部类对象被长期引用(如注册为监听器),外部类也无法回收;
- JNI 全局引用未释放:native 层调用 NewGlobalRef 后忘记 DeleteGlobalRef,会阻止 JVM 回收对应 Java 对象。
排查时可用 jmap -histo 查看对象数量分布,结合 jstack 分析线程栈,或使用 VisualVM / JProfiler 的“OQL”或“Heap Walker”功能手动从可疑 GC Root 出发反向追踪引用链。
弱引用、软引用与 GC Roots 的关系
GC Roots 本身只定义“强可达性”。对于不同引用强度的对象:
- 强引用:只要从 GC Roots 出发有强引用链,就不可回收;
- 软引用:仅被软引用链连接的对象,在内存不足时会被回收(不改变 GC Roots 定义,而是回收策略扩展);
- 弱引用:仅被弱引用链连接的对象,在每次 GC 标记阶段结束后即被回收(WeakHashMap 的 key 就是典型应用);
- 虚引用:不影响对象生命周期,仅用于在对象被回收后收到通知(需配合 ReferenceQueue 使用)。
这些引用类型不改变 GC Roots 的集合,但会影响“是否可达”的最终判定粒度——只有强引用链才参与初始标记。


















