可达性分析: 什么东西可以被当作根?
在JVM的垃圾回收(GC) 中,可达性分析(Reachability Analysis) 是判断对象是否“存活”的核心算法——通过判断对象是否能从“根节点(GC Roots)”出发被访问到,来决定是否回收该对象。
只有满足**“可作为GC Roots”条件**的对象,才会被标记为根节点。以下是JVM中典型的GC Roots来源,按类别清晰拆解:
一、虚拟机栈(栈帧中的本地变量表)
虚拟机栈(每个线程私有)中,当前正在执行的方法的栈帧(Stack Frame)里的本地变量表,存储的引用类型变量所指向的对象,会被设为GC Roots。
- 原理:这些变量是当前线程正在直接使用的对象(比如方法参数、局部变量),如果回收它们,会导致程序运行出错。
- 示例:
public void test() { // obj是当前方法栈帧本地变量表中的引用,指向的对象会被设为GC Roots Object obj = new Object(); System.out.println(obj); // 执行过程中,obj引用的对象始终是GC Roots }
- 注意:当方法执行结束(栈帧出栈),该变量的引用消失,对应的对象就不再是GC Roots的一部分。
二、方法区中的静态变量(类静态属性)
方法区(元空间/永久代)中,类的静态引用类型变量所指向的对象,会被设为GC Roots。
- 原理:静态变量属于“类级别的属性”,生命周期与类一致(类不被卸载,静态变量就一直存在),且被所有对象共享,必须作为根节点避免误回收。
- 示例:
public class MyClass { // 静态变量staticObj引用的对象,会被设为GC Roots(只要MyClass不被卸载) public static Object staticObj = new Object(); }
- 注意:若类被JVM卸载(如自定义类加载器加载的类),则其静态变量对应的对象会失去GC Roots身份。
三、方法区中的常量(运行时常量池)
方法区的运行时常量池中,常量类型的引用所指向的对象,会被设为GC Roots。
- 原理:常量(如
String
常量池中的字符串引用、final
修饰的常量)在程序运行期间通常不会被修改,且可能被多处引用,必须作为根节点。 - 示例:
public class MyClass { // 常量CONST_OBJ引用的对象,会被设为GC Roots public static final Object CONST_OBJ = new Object(); }
- 典型场景:
String s = "hello";
中,“hello”是字符串常量池中的常量,其引用指向的对象会被设为GC Roots。
四、本地方法栈中的JNI引用(Native方法引用)
本地方法栈(存储Native方法执行时的栈帧)中,Native方法通过JNI(Java Native Interface)调用时使用的引用类型变量(如jobject
、jclass
等),所指向的对象会被设为GC Roots。
- 原理:Native方法(如调用C/C++实现的方法)是JVM外部代码,其引用的Java对象无法通过Java层面的栈/方法区追踪,因此必须作为根节点,防止被误回收。
- 示例:若Java代码调用一个Native方法
native void process(Object data);
,则data
参数在Native方法执行期间,其指向的对象会被设为GC Roots。
五、活跃的线程对象
JVM中正在运行的线程对象(Thread
实例) 本身,会被设为GC Roots。
- 原理:线程是程序执行的基本单位,只要线程还在运行(未终止),其关联的资源(如栈帧、本地变量)都需要保留,因此线程对象本身必须是根节点。
- 注意:即使线程处于阻塞状态(如
sleep()
、wait()
),只要未终止,仍会被视为GC Roots。
六、JVM内部的引用(系统级引用)
JVM自身运行过程中需要依赖的一些“系统级对象”,其引用也会被设为GC Roots,包括:
- 垃圾回收器自身的引用(如GC标记过程中使用的临时对象);
- JVM的类加载器(如启动类加载器
Bootstrap ClassLoader
); - 系统类(如
java.lang.Object
、java.lang.Class
等核心类)的引用; - JVM内部的线程(如Finalizer线程,负责执行对象的
finalize()
方法)。
这些对象是JVM正常运行的基础,绝对不能被回收,因此必须作为根节点。
总结:GC Roots的核心类别
根节点来源 | 具体对象/引用 | 核心原因 |
---|---|---|
虚拟机栈 | 当前方法栈帧本地变量表中的引用 | 程序正在使用的对象,不可回收 |
方法区-静态变量 | 类的静态引用类型变量 | 生命周期与类一致,全局共享 |
方法区-常量 | 运行时常量池中的引用(如String常量) | 常量不可修改,可能被多处引用 |
本地方法栈 | Native方法的JNI引用(如jobject) | 外部Native代码依赖的Java对象 |
活跃线程 | 正在运行/阻塞的Thread实例 | 线程未终止,关联资源需保留 |
JVM内部引用 | GC自身引用、类加载器、系统类等 | 维持JVM正常运行的基础对象 |
理解GC Roots的关键:这些对象是“绝对不可能被回收”的,因为它们直接或间接被程序运行、JVM自身所依赖。可达性分析的本质,就是从这些根节点出发,遍历对象引用链,未被遍历到的对象就是“不可达”的,最终会被GC回收。