
本文深入剖析java magazine一道经典可达性题目,揭示arraylist.remove()引发的索引偏移效应及非静态内部类隐式引用机制,说明car对象在执行完第12行后依然强可达、无法被gc回收的根本原因。
本文深入剖析java magazine一道经典可达性题目,揭示arraylist.remove()引发的索引偏移效应及非静态内部类隐式引用机制,说明car对象在执行完第12行后依然强可达、无法被gc回收的根本原因。
要准确判断一个对象是否“有资格被垃圾回收”,核心在于分析其是否仍被任何强引用链可达(reachable)。本题的关键陷阱在于两处易被忽略的Java语言细节:ArrayList.remove()的索引动态偏移行为和非静态内部类对 enclosing instance 的隐式强引用。
我们逐行追踪关键对象的引用状态(重点关注 car 和 clutch):
01: var rl = new ArrayList<Repairable>(); // 创建空列表
02: var car = new Car(); // car 引用指向新Car实例
03: var clutch = car.getClutch(); // 假设getClutch()返回Car的非静态内部类实例(如Car.Clutch)
04: var engine = (Repairable) null; // engine == null
05: rl.add(car); // rl[0] = car
06: rl.add(clutch); // rl[1] = clutch(注意:clutch隐式持有car引用!)
07: car = null; // car局部变量置null,但car仍被rl[0]和clutch间接持有
08: clutch = null; // clutch局部变量置null,但clutch仍被rl[1]持有
09: rl.add(engine); // rl[2] = null
10: rl.set(2, engine); // rl[2] 仍为 null(冗余操作)
11: rl.remove(0); // 移除rl[0](即car),此时rl变为 [clutch, null]
// 注意:原rl[1](clutch)现在位于索引0,原rl[2](null)现在位于索引1
12: rl.remove(1); // 移除当前索引1的元素 → 即null!clutch仍在索引0位置✅ 关键结论一:索引偏移remove(0) 后,列表元素前移,clutch 从索引 1 变为索引 0;因此 remove(1) 实际删除的是 原索引2位置的 null,而非 clutch。执行完第12行后,rl 中仅剩一个元素:rl.get(0) == clutch。
✅ 关键结论二:隐式强引用
若 clutch 是 Car 的非静态内部类(这是题干隐含前提,也是Java Magazine原文上下文所设定),则每个 clutch 实例都持有一个不可见的 final Car this$0 字段,该字段强引用着其外围 Car 实例。因此,只要 clutch 本身可达(此处被 rl[0] 强引用),它所引用的 car 就必然可达。
? 注意:若
clutch是静态内部类、独立类或不持有Car引用的普通对象,则上述结论不成立。但本题语境明确依赖非静态内部类的语义。立即学习“Java免费学习笔记(深入)”;
因此,直至第12行执行完毕:
-
clutch仍被rl.get(0)强引用; -
car通过clutch.this$0被强引用; -
car仍处于 GC Root 可达路径上 → 不具备GC资格。
? 延伸提醒:
-
ArrayList.remove(int index)是基于当前快照索引的操作,与初始插入位置无关; - 分析可达性时,必须考虑所有可能的引用路径,包括编译器自动生成的隐式引用;
- 使用
WeakReference或将内部类声明为static是打破此类隐式强引用的常用手段。
综上,Java Magazine的答案正确:Car 对象在第12行后依然不可被垃圾回收。


















