在 Java 中,哪些对象可以作为 GC Roots?
在 Java 中,垃圾回收器(Garbage Collector)使用可达性分析算法(Reachability Analysis)来判断对象是否存活。这个算法的基本思想是:通过一系列被称为 "GC Roots"(垃圾回收根节点) 的对象作为起点,向下搜索,如果一个对象到 GC Roots 之间没有任何引用链相连(即不可达),那么这个对象就被认为是垃圾,可以被回收。
在 Java(特别是主流的 HotSpot 虚拟机)中,可以作为 GC Roots 的对象主要包括以下几大类:
1. 核心的四大类(面试最常考)
- 虚拟机栈(栈帧中的局部变量表)中引用的对象
- 解释:各个线程正在执行的方法里的局部变量、参数等。
- 举例:你在某个方法里写了
Object obj = new Object();,只要这个方法还没执行完,obj变量对应的对象就是 GC Root 之一。
- 方法区中类静态属性引用的对象
- 解释:Java 类的引用类型静态变量。
- 举例:
public static MyClass myClass = new MyClass();,这个myClass引用的对象就是 GC Root。只要该类不被卸载,它引用的对象就不会被回收。
- 方法区中常量引用的对象
- 解释:比如字符串常量池(String Table)里的引用,或者被
final修饰的引用类型常量。 - 举例:
public static final String NAME = "Java";
- 解释:比如字符串常量池(String Table)里的引用,或者被
- 本地方法栈中 JNI(即 Native 方法)引用的对象
- 解释:Java 调用 C/C++ 代码时,C/C++ 代码中持有的对 Java 对象的引用。
2. JVM 内部和系统级引用
除了上面常见的四种,JVM 内部还有一些长期存活的对象也被作为 GC Roots:
- Java 虚拟机内部的引用
- 基本数据类型对应的 Class 对象(如
int.class,boolean.class)。 - 一些常驻的异常对象(如
NullPointerException,OutOfMemoryError)。 - 系统类加载器(System Class Loader)。
- 基本数据类型对应的 Class 对象(如
- 所有被同步锁(
synchronized关键字)持有的对象- 解释:如果一个对象正在被用作锁,它绝对不能被回收,否则会破坏同步机制。
- 反映 Java 虚拟机内部情况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等
- 解释:用于 JVM 监控、调试的内部数据结构引用的对象。
- 活动线程(Active Threads)
- 解释:所有正在运行的
Thread对象本身。
- 解释:所有正在运行的
3. 临时加入的 GC Roots(跨代/跨区引用)
在现代垃圾回收器(如 G1、ZGC 以及传统的分代 GC)中,由于垃圾回收经常是局部回收(Partial GC),还会存在“临时 GC Roots”:
- 跨代引用(比如 Young GC 时的老年代对象)
- 解释:当只回收新生代(Young GC)时,如果有老年代的对象引用了新生代的对象,为了避免扫描整个老年代,JVM 会将这些包含跨代引用的老年代对象(通过卡表 Card Table 记录)临时加入到 GC Roots 的集合中。
💡 总结与记忆技巧
为了方便记忆,你可以按照内存区域来划分:
- 线程私有区域(活着的): 虚拟机栈(Java方法局部变量)、本地方法栈(Native方法变量)、运行中的线程。
- 线程共享区域(全局的): 方法区(静态变量、常量)。
- JVM 底层必备(内部的): 锁对象、JVM内部类、系统类加载器。
核心判断逻辑:只要是当前正在被直接使用(栈中的变量)、全局共享且生命周期长(静态/常量)、或者JVM运行不可或缺的对象,就是 GC Roots。