GC 判定算法
候选人小林在面试美团 P6 时,面试官问道:
"怎么判断一个对象是否应该被回收?"
小林说:"用引用计数法..."面试官追问:"引用计数法有什么问题?"
小林说:"有循环引用的问题..."面试官继续追问:"那 JVM 用的是什么算法?"
小林答不上来...
一、核心问题:GC 判定算法 🔴
1.1 问题拆解
1.2 ❌ 错误示范
候选人原话 A:"JVM 用的是引用计数法。"
问题诊断:JVM 用的是根可达算法(Reachability Analysis),不是引用计数法。引用计数法虽然简单,但无法处理循环引用。
候选人原话 B:"对象没有被引用就会被回收。"
问题诊断:对象即使没有被直接引用,如果通过引用链与 GC Roots 相连,仍然是可达的,不会被回收。
1.3 标准回答
P5 级别:两种判定算法
引用计数法(Reference Counting):
每个对象有一个引用计数器。当有新的引用指向对象时,计数器 +1;引用失效时,计数器 -1。计数器为 0 时,对象死亡。
优点:判定简单,回收及时 缺点:无法处理循环引用(A 引用 B,B 引用 A,但两者都不再被外部引用)
Python 使用引用计数 + GC:Python 虽然用引用计数回收大多数对象,但用额外的 GC 处理循环引用。
根可达算法(Reachability Analysis):
从一组根(GC Roots)出发,沿着引用链向下搜索。所有能够到达的对象是可达的(Reachable),存活;不能到达的对象是不可达的(Unreachable),可以回收。
JVM 使用根可达算法,因为它能正确处理循环引用。
P6 级别:GC Roots 的选择
可以作为 GC Roots 的对象:
方法区作为 GC Roots 的特殊性:
在 JDK 7 及之前,方法区的常量池中的字符串常量、静态引用可以作为 GC Roots。JDK 8 之后,字符串常量池移到了堆中。
P7 级别:引用的五种强度
JDK 1.2 引入的引用分类:
虚引用的特殊用途:
【面试官心理】 这道题我能问到 P7 级别,是因为 GC 判定算法涉及了垃圾回收的理论基础和引用分类的高级应用。能说清虚引用用途的候选人说明他理解了引用分类的工程价值。
1.4 追问升级
追问:方法区需要 GC 吗?什么情况下方法区会回收?
方法区也需要 GC,但不是所有内容都值得回收:
- 类卸载:当类的 ClassLoader 不再被引用,且该类没有实例时,可以卸载类(元数据回收)
- 常量池回收:运行时常量池中的常量(与字符串常量池不同)
二、生产避坑 🟡
2.1 内存泄漏的 GC 表现
GC 表现:Minor GC 后存活对象数量持续增长,最终进入老年代,导致频繁 Full GC。
2.2 软引用缓存
三、四种引用强度对比 🟢
面试加分点:能说出"JDK 9 引入了 java.lang.ref.Cleaner,替代了传统的 finalize() 方法,用于在对象被 GC 后执行清理任务(如释放堆外内存)",说明他关注了 JDK 9 的改进。
面试陷阱:被问到"循环引用的对象会被 JVM 回收吗",很多人会说"不会"。准确答案是:会回收。JVM 使用根可达算法,循环引用的对象如果无法从 GC Roots 到达,就会被回收。