
本文详解Oracle Java Magazine一道经典可达性题目,通过逐行分析ArrayList操作对引用关系的影响,阐明Car对象因Clutch的隐式强引用而始终保持可达,故在第12行后仍不满足GC条件。
本文详解oracle java magazine一道经典可达性题目,通过逐行分析`arraylist`操作对引用关系的影响,阐明`car`对象因`clutch`的隐式强引用而始终保持可达,故在第12行后仍**不满足gc条件**。
要准确判断一个Java对象是否“有资格被垃圾回收”,核心在于判定其是否不可达(unreachable)——即从任何GC Roots(如线程栈帧中的局部变量、静态字段、JNI引用等)出发,不存在任何强引用链可到达该对象。
我们来逐行追踪题中代码的引用关系变化(关键点已加粗):
01: var rl = new ArrayList<repairable>(); // 创建空列表
02: var car = new Car(); // car 引用指向新Car实例(GC Root → car → Car)
03: var clutch = car.getClutch(); // 假设getClutch()返回Car内部的Clutch实例;clutch持有对car的隐式引用(因Clutch很可能是Car的非静态内部类或持有this引用)
04: var engine = (Repairable) null; // engine == null
05: rl.add(car); // rl[0] = car → Car仍被rl间接引用
06: rl.add(clutch); // rl[1] = clutch → Clutch被引用;且clutch → car(强引用链存在)
07: car = null; // 局部变量car置null,但Car仍被rl[0]和clutch共同引用
08: clutch = null; // 局部变量clutch置null,但Clutch仍被rl[1]引用 → Car仍通过clutch间接可达
09: rl.add(engine); // rl[2] = null(此时rl = [car, clutch, null])
10: rl.set(2, engine); // rl[2]仍为null(无实质变化)
11: rl.remove(0); // 移除索引0元素(即car),rl变为 [clutch, null]
// ⚠️ 注意:remove后元素前移 → 原rl[1](clutch)现位于rl[0],原rl[2](null)现位于rl[1]
12: rl.remove(1); // 移除当前索引1处的元素 → 即移除null(不是clutch!)
// 此时rl = [clutch]</repairable>
关键误区澄清:
❌ 错误理解:“rl.remove(1) 删除的是 clutch”
✅ 正确事实:remove(int index) 操作会动态重排索引。第11行 remove(0) 后,clutch 已从索引1迁移至索引0;第12行 remove(1) 实际删除的是列表中当前索引1位置的 null 元素,clutch 依然稳居 rl[0]。
更重要的是——clutch 对象本身持有对 Car 的强引用(例如:若 Clutch 是 Car 的非静态内部类,其隐式包含对外围类 Car 的 this$0 引用;或显式保存了 carRef 字段)。因此只要 clutch 本身可达(它正被 rl[0] 强引用),Car 就始终处于可达状态。
最终状态(第12行执行完毕后):
-
rl = [clutch](非空,且含唯一元素) -
clutch可达 →Car可达(通过clutch的隐式/显式引用) - 无其他局部变量或静态引用指向
Car - ✅
Car对象不可被GC —— Oracle答案完全正确。
? 补充提醒:
-
ArrayList.remove(int)是基于当前索引的操作,非基于原始插入顺序; - 内部类引用外围实例是常见可达性“陷阱”,务必检查
Clutch的实现细节(题目上下文默认其维持对Car的强引用); -
null元素本身不构成引用,但容器中存储的非null对象引用才是可达性的关键载体。
结论:对象GC资格判定必须结合运行时实际引用图,而非静态代码行号。本例中,Car 因 clutch 的持续持有而全程可达——理解索引动态变化与隐式引用链,是避免此类误判的核心。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











