
本文通过分析一段典型java代码,详解对象在arraylist中如何因remove等操作保持可达性,揭示索引变化对垃圾回收的隐含影响。
本文通过分析一段典型java代码,详解对象在arraylist中如何因remove等操作保持可达性,揭示索引变化对垃圾回收的隐含影响。
在Java内存管理中,“对象是否可达(reachable)”是决定其能否被垃圾回收器(GC)回收的核心判定标准。根据JVM规范,一个对象若能通过任何一条从GC Roots出发的引用链被访问到,即被视为“可达”,不会被回收。而本例的关键陷阱在于:开发者常忽略ArrayList.remove(int index)方法会自动重排后续元素索引这一行为,从而误判对象的存活状态。
我们逐行追踪题干代码中ArrayList<Repairable> rl的演化过程(假设Car构造时创建了Clutch实例,且getClutch()返回该内部引用):
01: var rl = new ArrayList<Repairable>(); // 空列表 [] 02: var car = new Car(); // car → Car实例(含clutch引用) 03: var clutch = car.getClutch(); // clutch → Clutch实例(被car强引用) 04: var engine = (Repairable) null; // engine == null 05: rl.add(car); // rl = [car] 06: rl.add(clutch); // rl = [car, clutch] 07: car = null; // car局部变量置null,但rl[0]仍持有car引用 → car仍可达 08: clutch = null; // clutch局部变量置null,但rl[1]仍持有clutch引用 → clutch仍可达 09: rl.add(engine); // rl = [car, clutch, null] 10: rl.set(2, engine); // 重复设为null,无实质变化 → rl = [car, clutch, null] 11: rl.remove(0); // 移除索引0(car),后续元素前移 → rl = [clutch, null] 12: rl.remove(1); // 移除索引1(当前为null),clutch前移至索引0 → rl = [clutch]
关键点在于:remove(1)操作移除的是当时位于索引1位置的null,而非原始clutch。由于remove会触发数组收缩与索引重排,clutch在第11行后已占据索引0,因此它始终未被移除,且持续被rl中的唯一元素所引用。
由此可得结论:
✅ clutch对象在整个执行过程中始终保持可达——它既曾被car间接引用(行3–7),又被rl直接引用(行6、11后持续存在);
✅ car对象在行11被移除前也一直可达;但行11之后,car不再被任何变量或容器引用,变为不可达,可被GC回收;
❌ 原题解析中“car实例也仍然存活”的说法是错误的——car在行11执行后即失去所有强引用,仅clutch因保留在rl中而持续存活。
⚠️ 注意事项:
立即学习“Java免费学习笔记(深入)”;
- ArrayList.remove(int) 修改的是当前快照下的索引位置,非原始插入序号;
- null本身不是对象,不占用堆内存,但ArrayList中存储null仍会保留该槽位(直到被覆盖或移除);
- 判断可达性必须基于执行到某一行后的最新引用图,而非代码字面顺序。
因此,正确理解容器操作引发的引用关系动态变化,是精准掌握Java对象生命周期的前提。在性能敏感或资源受限场景(如Android、嵌入式Java),此类细节直接影响内存泄漏风险与GC压力。


















