# 38. GC Root主要包含哪些对象?
# 标准答案
GC Root(垃圾回收根对象)是 JVM 在执行垃圾回收时的起始点,所有直接或间接被 GC Root 引用的对象都不会被回收。GC Root 主要包括以下几类对象:
- 栈中的引用对象(即 Java 方法执行时,虚拟机栈中局部变量表中的对象引用)
- 方法区中的静态变量(即类的静态成员变量,存放在方法区或 Metaspace)
- 方法区中的常量引用(例如 String Pool 中的字符串常量)
- JNI(Native 方法)的引用对象(即本地方法调用过程中引用的对象,如 DirectByteBuffer 关联的内存对象)
- 正在同步的对象(同步锁 Monitor 对象)(被 synchronized 关键字持有的对象)
# 答案解析
在垃圾回收过程中,GC 需要找到存活对象,避免误删仍在使用的对象。JVM 通过**可达性分析算法(Reachability Analysis)**来判断对象是否存活:如果一个对象可通过 GC Root 直接或间接到达,则它被认为是存活的,否则就是垃圾对象,会被 GC 回收。
# GC Root 的主要组成部分
栈中的引用对象(局部变量表中的对象引用)
- 当一个 Java 线程运行时,它会创建虚拟机栈(Thread Stack),方法执行过程中,方法栈帧的局部变量表中存储着对象的引用。如果某个对象的引用仍然存在于局部变量表中,那么这个对象不会被回收。
- 例如:
public void test() { Object obj = new Object(); // obj 是 GC Root } // obj 作用域结束后,可能变成垃圾对象
1
2
3 - GC 过程中,局部变量表中的引用作为起点进行可达性分析。
方法区中的静态变量
- 静态变量是类加载后一直存活的,存放在方法区(JDK 8 及以后在 Metaspace),只要类没有被卸载,静态变量所引用的对象也不会被回收。例如:
public class Example { private static Object staticObj = new Object(); // staticObj 是 GC Root }
1
2
3 - 静态变量引用的对象不会被 GC 直接回收,除非类被卸载。
- 静态变量是类加载后一直存活的,存放在方法区(JDK 8 及以后在 Metaspace),只要类没有被卸载,静态变量所引用的对象也不会被回收。例如:
方法区中的常量引用
- 常量池存储在方法区中,字符串常量(如 String Pool 中的字符串)也是 GC Root。例如:
String str = "hello"; // str 指向的是字符串常量池中的"hello",不会被回收
1 - 字符串常量池中的对象在没有外部引用时仍可能被回收,但通常不会主动清理。
- 常量池存储在方法区中,字符串常量(如 String Pool 中的字符串)也是 GC Root。例如:
JNI(Native 方法)的引用对象
- 当 Java 代码调用 Native 代码(C/C++ 编写的本地方法)时,本地代码可能会持有对象的引用,使其不会被 GC 回收。例如:
ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 由 JNI 持有
1 - 直接分配的堆外内存(如 DirectByteBuffer)是由 JNI 代码管理的,可能不会被 Java GC 直接回收,需要手动释放。
- 当 Java 代码调用 Native 代码(C/C++ 编写的本地方法)时,本地代码可能会持有对象的引用,使其不会被 GC 回收。例如:
正在同步的对象(持有 Monitor 锁的对象)
- 当一个对象被 synchronized 关键字锁定时,它会被 JVM 认为是 GC Root。例如:
public class SyncExample { private static final Object lock = new Object(); public void method() { synchronized (lock) { // lock 作为 GC Root } } }
1
2
3
4
5
6
7
8
9 - 持有锁的对象在 GC 期间不会被回收,直到锁释放。
- 当一个对象被 synchronized 关键字锁定时,它会被 JVM 认为是 GC Root。例如:
# GC Root 相关的常见误区
GC Root 不是所有存活对象的集合
- GC Root 仅仅是垃圾回收的起始点,并不代表所有存活对象,而是通过可达性分析找到的对象才不会被回收。
静态变量不等于不会被回收
- 只有静态变量本身是 GC Root,但如果静态变量被置为 null,或者类被卸载,那么原来的对象仍然可能被回收。
GC Root 也可能被回收
- 某些 GC Root(如线程栈中的局部变量)在方法执行完毕后会被销毁,进而使得某些对象失去引用而被回收。
# 深入追问
如何在 JVM 运行时查看当前 GC Root?
- 通过 jmap -histo 或 MAT(Memory Analyzer Tool) 工具分析 GC Root 的引用路径。
哪些情况会导致 GC Root 变成不可达对象?
- 线程结束、类卸载、Native 代码释放对象引用等。
为什么 DirectByteBuffer 不受 GC 直接管理?
- 因为 DirectByteBuffer 使用的是堆外内存,由 JNI 代码管理,GC 只能通过 ReferenceQueue 进行间接管理。
# 相关面试题
- 如何手动查看 GC Root 并分析内存泄漏?
- 哪些对象在 Minor GC 和 Full GC 时不会被回收?
- 如何使用 MAT 分析 GC Root 引用链