如何判断对象是否可回收?引用计数与可达性分析有何区别?
结论先行:主流 JVM(HotSpot)使用可达性分析(GC Roots Tracing):从 GC Roots 出发沿引用链向下搜索,未被任何引用链触达的对象判定为不可达、可回收。引用计数因无法解决循环引用且维护计数有额外开销,已不被主流实现采用。
两种判定算法对比
| 对比项 | 引用计数 | 可达性分析 |
|---|---|---|
| 原理 | 被引用一次计数 +1,失效 -1,归零即回收 | 从 GC Roots 遍历引用链,不可达即回收 |
| 循环引用 | 无法处理(A↔B 互指则计数永不归零) | 天然免疫,整环一起回收 |
| 额外开销 | 每次引用赋值/变化都要同步计数 | 只在 GC 停顿期遍历一次 |
| 使用现状 | 部分语言辅助使用,JVM 不采用 | HotSpot 默认方案 |
哪些对象能当 GC Roots
- 虚拟机栈(栈帧局部变量表)中引用的对象;
- 方法区静态属性、常量引用的对象;
- 本地方法栈中 JNI 引用的对象;
- 被同步锁(synchronized)持有的对象等;
- 扩展补充:运行时常量池引用的对象、活跃线程对象、JNI 全局引用也常被归入 GC Roots。
public class CycleRefDemo {
static class Node { Node next; }
public static void main(String[] args) {
Node a = new Node();
Node b = new Node();
a.next = b; // 构造循环引用
b.next = a;
a = null;
b = null;
// 引用计数下两者计数恒为 1 → 泄漏;
// 可达性分析下整环均不可达 → 可正常回收
}
}
常见追问 / 记忆点
- 记忆点:答“如何判断可回收”先答可达性分析,再补一句引用计数死于循环引用。
- 追问:强/软/弱/虚四种引用影响回收时机:软引用内存不足才回收、弱引用下次 GC 就回收、虚引用配合堆外内存回收。
- 追问:能当 GC Roots 的引用一定活在栈、静态区或 JNI 中,面试举这三类基本够用。