# 38. GC Root主要包含哪些对象?

# 标准答案

GC Root(垃圾回收根对象)是 JVM 在执行垃圾回收时的起始点,所有直接或间接被 GC Root 引用的对象都不会被回收。GC Root 主要包括以下几类对象:

  1. 栈中的引用对象(即 Java 方法执行时,虚拟机栈中局部变量表中的对象引用)
  2. 方法区中的静态变量(即类的静态成员变量,存放在方法区或 Metaspace)
  3. 方法区中的常量引用(例如 String Pool 中的字符串常量)
  4. JNI(Native 方法)的引用对象(即本地方法调用过程中引用的对象,如 DirectByteBuffer 关联的内存对象)
  5. 正在同步的对象(同步锁 Monitor 对象)(被 synchronized 关键字持有的对象)

# 答案解析

在垃圾回收过程中,GC 需要找到存活对象,避免误删仍在使用的对象。JVM 通过**可达性分析算法(Reachability Analysis)**来判断对象是否存活:如果一个对象可通过 GC Root 直接或间接到达,则它被认为是存活的,否则就是垃圾对象,会被 GC 回收。

# GC Root 的主要组成部分

  1. 栈中的引用对象(局部变量表中的对象引用)

    • 当一个 Java 线程运行时,它会创建虚拟机栈(Thread Stack),方法执行过程中,方法栈帧的局部变量表中存储着对象的引用。如果某个对象的引用仍然存在于局部变量表中,那么这个对象不会被回收。
    • 例如:
      public void test() {
          Object obj = new Object(); // obj 是 GC Root
      } // obj 作用域结束后,可能变成垃圾对象
      
      1
      2
      3
    • GC 过程中,局部变量表中的引用作为起点进行可达性分析。
  2. 方法区中的静态变量

    • 静态变量是类加载后一直存活的,存放在方法区(JDK 8 及以后在 Metaspace),只要类没有被卸载,静态变量所引用的对象也不会被回收。例如:
      public class Example {
          private static Object staticObj = new Object(); // staticObj 是 GC Root
      }
      
      1
      2
      3
    • 静态变量引用的对象不会被 GC 直接回收,除非类被卸载。
  3. 方法区中的常量引用

    • 常量池存储在方法区中,字符串常量(如 String Pool 中的字符串)也是 GC Root。例如:
      String str = "hello"; // str 指向的是字符串常量池中的"hello",不会被回收
      
      1
    • 字符串常量池中的对象在没有外部引用时仍可能被回收,但通常不会主动清理。
  4. JNI(Native 方法)的引用对象

    • 当 Java 代码调用 Native 代码(C/C++ 编写的本地方法)时,本地代码可能会持有对象的引用,使其不会被 GC 回收。例如:
      ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 由 JNI 持有
      
      1
    • 直接分配的堆外内存(如 DirectByteBuffer)是由 JNI 代码管理的,可能不会被 Java GC 直接回收,需要手动释放。
  5. 正在同步的对象(持有 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 期间不会被回收,直到锁释放。

# GC Root 相关的常见误区

  1. GC Root 不是所有存活对象的集合

    • GC Root 仅仅是垃圾回收的起始点,并不代表所有存活对象,而是通过可达性分析找到的对象才不会被回收。
  2. 静态变量不等于不会被回收

    • 只有静态变量本身是 GC Root,但如果静态变量被置为 null,或者类被卸载,那么原来的对象仍然可能被回收。
  3. GC Root 也可能被回收

    • 某些 GC Root(如线程栈中的局部变量)在方法执行完毕后会被销毁,进而使得某些对象失去引用而被回收。

# 深入追问

  • 如何在 JVM 运行时查看当前 GC Root?

    • 通过 jmap -histoMAT(Memory Analyzer Tool) 工具分析 GC Root 的引用路径。
  • 哪些情况会导致 GC Root 变成不可达对象?

    • 线程结束、类卸载、Native 代码释放对象引用等。
  • 为什么 DirectByteBuffer 不受 GC 直接管理?

    • 因为 DirectByteBuffer 使用的是堆外内存,由 JNI 代码管理,GC 只能通过 ReferenceQueue 进行间接管理。

# 相关面试题

  • 如何手动查看 GC Root 并分析内存泄漏?
  • 哪些对象在 Minor GC 和 Full GC 时不会被回收?
  • 如何使用 MAT 分析 GC Root 引用链