《深入理解JVM》中对GC Roots的解释比较宽泛,导致不理解,这里简单解释一下
原文解释
在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
- 所有被同步锁(synchronized关键字)持有的对象。
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
更容易理解的解释
1. 虚拟机栈(栈帧中的本地变量表)中的引用对象
-
直白解释: 只要方法还没运行完,方法里定义的变量就不能删。
-
场景: 比如你在
main方法里写了val user = User(),这里的user就是GC Root
fun main() {
val user = User()
print(user.name)
}
-
为什么是 Root: 此时
user变量存在于当前线程的栈帧里。如果 GC 把这个User对象回收了,那你接下来的代码user.name()就会崩溃。 -
总结: 正在运行的方法里用到的参数、局部变量都是 Root。
方法区中类静态属性引用的对象
-
直白解释: 被
static修饰的属性(注意是属性,而不是属性引用的那个对象),它们属于这个类,不属于某个对象。 -
场景:
public class Test {
public static Object cache = new Object();
}
这里抠一下字眼:按照书上的解释,
cache就是被static修饰的属性,而cache指向的new Object就是GC Root,其实这是错误的❌。
真正的 GC Root,是这个 cache.
GC 的搜索逻辑是:
- 走到方法区,找到类。
- 看到类里有个静态变量
cache。- 把
cache标记为 Root。- 顺着
cache存的地址,去堆里找,找到了那个Object。- 宣布这个
Object是活的,不能被回收
-
为什么是 Root: 静态变量的生命周期非常长,通常和类一致(直到类加载器卸载),它们往往被设计为全局共享,所以它们引用的对象必须是活的。
-
总结: 全局变量(静态变量)是
Root。
方法区中常量引用的对象
-
直白解释: 主要是字符串常量池里的引用。
-
场景:
String s = "Hello World"; -
为什么是 Root: 常量意味着 “不可变” 且 “长期存在”,为了性能,Java 会把字符串存在常量池里供全局复用,既然要复用,就不能随随便便被回收。
-
总结: 常量池里的硬引用是
Root。
本地方法栈中 JNI 引用的对象
-
直白解释: 当 Java 调用 C/C++ 代码(Native 方法)时,C/C++ 代码里可能也引用了 Java 对象。
-
为什么是 Root: 虚拟机内部的 GC 管不了 C/C++ 的内存空间。如果 Java 这边把对象回收了,但 C/C++ 那边还在通过指针操作这个对象,就会导致系统崩溃。
-
总结: 跨语言调用时,对方正在用的 Java 对象是 Root。
5. Java 虚拟机内部的引用
-
直白解释: JVM 运行赖以生存的“基石”。
-
场景:
Object.class,Integer.class,或者是像NullPointerException这种经常被抛出的预分配异常对象。 -
为什么是 Root: 如果连
Object的类定义都被回收了,整个 Java 环境就瘫痪了。 -
总结: 系统级必备对象是
Root。
6. 所有被同步锁(synchronized)持有的对象
- 直白解释: 正在被用来当“锁”的对象。
注意这里的「被用来当锁」,锁持有这个对象,其实是在用这个对象的锁🔒
-
场景:
synchronized(myLock) { ... }只要有一个线程还没退出这个同步块,myLock对象就不能动。 -
为什么是 Root: 锁是多线程协作的关键。如果锁对象被回收了,线程之间的同步状态(谁持有锁、谁在等待)就会丢失,后果不堪设想。
-
总结: 正在发挥“锁”作用的对象是
Root。
7. JVM 内部情况的 JMXBean、JVMTI 注册的回调等
这一条不用记,可以忽略
-
直白解释: 监控和调试工具留下的“钩子”。
-
场景: 当你打开 VisualVM 观察内存,或者用 Java Agent 做性能监控时,JVM 需要维护一些回调函数和状态对象。
-
为什么是 Root: 这些对象是为了让外部工具能“看透”虚拟机。如果它们被回收了,监控和调试功能就失效了。
-
总结: 监控和调试系统关联的对象是
Root。