什么是 GC Roots ?
- JVM 是如何判断哪些对象应该回收,哪些应该保留呢?
- GC Roots
堆是一个巨大的对象集合,其中包含许多对象实例。
这些对象在堆中有不同的引用层次。一些接口会被频繁调用,每秒生成大量对象。这些对象之间形成复杂的联系网络。尽管 Java 一直试图创造一种永无止境的内存感觉,但对象不能一直增加下去,因此需要进行垃圾回收。
JVM 是如何判断哪些对象应该回收,哪些应该保留呢?
古代有“诛九族”的想法。如果有人犯了大错,皇帝杀死一个人不足以平息他的怒火,那么会连坐其亲友。诛九族需要追溯到一个共同的祖先,然后向下延伸。在堆的垃圾回收中也有类似的思路。下面我们具体分析 JVM 如何执行垃圾回收。
JVM 的垃圾收集动作是不受程序控制的,会在满足条件时自动启动。
在进行垃圾回收时,JVM 能够追踪到对象的祖先引用。最终发现,如果这些祖先已经不再存在,它们将被清理掉。那些能够逃过垃圾回收的祖先非常特殊,它们被称为 GC Roots。
GC Roots
通过从 GC Roots 开始向下追溯和搜索,形成一个称为“Reference Chain”(引用链)的链条。当一个对象无法与任何 GC Root 建立关联时,它将被无情地清除。
例如,Obj5、Obj6、Obj7 由于无法与 GC Roots 关联,将在垃圾回收时被销毁。
垃圾回收就是围绕着 GC Roots 去做的。同时,它也是很多内存泄露的根源,因为其他引用根本没有这样的权利。
GC Roots 是一组必须活跃的引用。用通俗的话来说,就是程序接下来通过直接引用或者间接引用,能够访问到的潜在被使用的对象
GC Roots 包括:
-
虚拟机栈中引用的对象:
- 当一个对象在虚拟机栈(栈帧中的本地变量表)中被引用时,它充当了 GC Root 的作用。例如:
public class Example { public static void main(String[] args) { Example obj = new Example(); // obj 是 GC Root obj = null; // 断开 obj 对原对象的引用 } }
- 在上面的示例中,
obj
是虚拟机栈中的本地变量,当obj
被赋值为null
时,原对象与 GC Root 断开连接,因此原对象会被回收。
- 当一个对象在虚拟机栈(栈帧中的本地变量表)中被引用时,它充当了 GC Root 的作用。例如:
-
方法区中类静态属性引用的对象:
- 类静态属性引用的对象也是 GC Roots。例如:
public class Example { public static Example s; // 类静态属性引用的对象 public static void main(String[] args) { Example obj = new Example(); obj.s = new Example(); // s 是 GC Root obj = null; // 断开 obj 对原对象的引用 } }
- 在上面的示例中,当栈帧中的本地变量 obj = null 时,由于 obj 原来指向的对象与 GC Root (变量 obj) 断开了连接,所以 obj 原来指向的对象会被回收,而由于我们给 s 赋值了变量的引用,s 在此时是类静态属性引用,充当了 GC Root 的作用,它指向的对象依然存活
- 类静态属性引用的对象也是 GC Roots。例如:
-
方法区中常量引用的对象:
- 常量引用的对象也不会因为其他引用断开而被回收。例如:
public class Example { public static final Example CONSTANT = new Example(); // 常量引用的对象 public static void main(String[] args) { Example obj = new Example(); obj = null; // 断开 obj 对原对象的引用,但 CONSTANT 不受影响 } }
- 在上面的示例中,常量
CONSTANT
指向的对象不会因为obj
的断开而被回收。
- 常量引用的对象也不会因为其他引用断开而被回收。例如:
-
本地方法栈中 JNI 引用的对象:
- JNI(Java Native Interface)是 Java 调用非 Java 代码的接口,本地方法栈中 JNI 引用的对象也是 GC Roots。例如:
JNIEXPORT void JNICALL Java_com_pecuyu_jnirefdemo_MainActivity_newStringNative(JNIEnv *env, jobject instance,jstring jmsg) {
...
// 缓存String的class
jclass jc = (*env)->FindClass(env, STRING_PATH);
}
有两个注意点:
- 我们这里说的是活跃的引用,而不是对象,对象是不能作为 GC Roots 的。
- GC 过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收它们占用的空间。所以,哪怕 JVM 的堆非常的大,基于 tracing 的 GC 方式,回收速度也会非常快。