GC 的判定方法
Java 中 GC(Garbage Collection)判定对象是否可回收的方法,包括原理、常用算法、优缺点,让你彻底理解 JVM 是如何判断一个对象可以被垃圾回收的。
1. GC 判定的核心问题
在 Java 中,GC 的核心任务是:
判断哪些对象已经不再被引用,可以安全地释放内存。
JVM 需要一种机制来判断对象是否存活(可达)还是死亡(不可达)。
2. 常见判定方法
Java 中主要有两种判定对象是否可回收的方法:
2.1 引用计数法(Reference Counting)
原理:
- 每个对象维护一个引用计数器。
- 当有一个地方引用它时,计数器 +1。
- 当引用失效时,计数器 -1。
- 当计数器为 0 时,说明对象不可达,可以回收。
优点:
- 实现简单,判定效率高。
- 对象可以立即被回收,不需要等待 GC 扫描。
缺点:
- 无法解决循环引用问题:
class Node {Node next; }Node a = new Node(); Node b = new Node(); a.next = b; b.next = a; // 循环引用
a
和b
不再被外部引用,它们的计数器仍然 > 0,导致无法回收。
结论:
- Java 的主流 GC 不使用引用计数法,但在一些语言(如 Python)中仍有应用。
2.2 可达性分析法(Reachability Analysis)
原理:
- 以一组称为 GC Roots 的对象为起点,沿着引用链向下搜索。
- 能够从 GC Roots 到达的对象,称为可达对象(存活)。
- 无法到达的对象,称为不可达对象(死亡,可回收)。
GC Roots 常见类型:
- 虚拟机栈中引用的对象(方法参数、局部变量等)。
- 方法区中类的静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中 JNI 引用的对象。
- 正在运行的线程对象。
- 类加载器引用的对象。
优点:
- 能够解决循环引用问题。
- 是 Java GC 的主流判定方法。
缺点:
- 需要从 GC Roots 遍历整个对象图,开销比引用计数法大。
3. 判定流程(可达性分析法)
- 确定 GC Roots 集合。
- 从 GC Roots 出发,遍历引用链。
- 标记所有可达对象。
- 未被标记的对象即为不可达对象 → 可回收。
4. 特殊情况:对象的“自救”
在可达性分析中,如果对象不可达,并不一定立刻被回收:
- JVM 会调用对象的
finalize()
方法(如果重写了)。 - 在
finalize()
中,如果对象重新建立引用链(如赋值给 GC Roots 可达对象),它可以“复活”。 - 但
finalize()
只会被调用一次,且不推荐使用(性能差、不可预测)。
5. 总结对比
方法 | 原理 | 优点 | 缺点 | Java 是否使用 |
---|---|---|---|---|
引用计数法 | 计数器记录引用次数 | 实现简单,判定快 | 循环引用无法回收 | ❌ |
可达性分析法 | 从 GC Roots 遍历引用链 | 可解决循环引用 | 遍历开销大 | ✅ |
6. 总结
- Java GC 判定对象可回收主要使用可达性分析法。
- GC Roots 是判定的起点,沿引用链遍历对象图。
- 引用计数法简单但有循环引用问题,因此 Java 不采用。
- 对象在被判定为不可达后,可能通过
finalize()
方法短暂“自救”,但不推荐依赖此机制。