arraylist中对象不被回收是因为集合持有强引用,gc无法回收仍可达的对象;典型原因包括未清空集合、缓存未淘汰、监听器未注销,导致outofmemoryerror及堆内存中dto/vo等实例堆积。

为什么 ArrayList 里的对象不被回收?
不是 JVM 不想回收,而是你代码里还留着对它的强引用——最常见的是集合没清空、缓存没淘汰、监听器没注销。Java 的 GC 只能回收「不可达」对象,而 ArrayList、HashMap 这类容器一旦持有对象,就构成强引用链,哪怕业务逻辑早就不用它了。
典型现象:OutOfMemoryError: Java heap space 出现在长期运行的服务里,堆 dump 显示大量本该被清理的 DTO、VO 或 Handler 实例堆积在 ArrayList.elementData 或 HashMap.table 中。
- 别依赖「方法结束」自动释放:局部变量引用消失 ≠ 集合内部引用消失
- 注意
ArrayList.remove()只是把索引位置设为null(JDK 7+),但elementData数组本身可能仍持有旧引用,尤其调用过trimToSize()前 - 用
WeakReference包装缓存值时,记得用WeakHashMap而非手动存WeakReference到HashMap,否则 key 仍是强引用
remove() 之后还要手动置 null 吗?
大多数情况不用——但前提是你知道自己在用哪个集合、哪个 JDK 版本、以及是否做过扩容收缩操作。
JDK 7 及以后,ArrayList.remove(int) 和 remove(Object) 内部都会显式把对应数组槽位设为 null;LinkedList 的 remove() 也会断开前后节点引用。但这些「自动置空」只作用于被删元素本身,不涉及其他未操作位置。
立即学习“Java免费学习笔记(深入)”;
- 如果你调用过
ArrayList.ensureCapacity(1000)或初始容量设得极大,然后只 add 了几个对象,那elementData数组前几百个槽位仍为null(安全),但后面大量未初始化位置其实也占着引用槽——不过这是 JVM 层面的数组对象头开销,不是泄露主因 - 真正要手动置
null的场景:自定义缓存结构(比如用Object[] cache手写 LRU)、静态集合长期持有、或使用了Vector/Stack等遗留类(它们的remove()不清空数组) - 验证方式:用
jmap -histo:live <pid></pid>对比 remove 前后某类实例数,再看 MAT 中 dominator tree 是否仍有路径指向它
静态集合 + 内部类引用 = 泄露高发区
静态集合本身生命周期与类加载器绑定,只要类没卸载,它就一直活着;而如果往里面塞了非静态内部类实例(比如匿名 Runnable、Lambda 表达式捕获了外部 this),就会把整个外部对象钉在堆里。
错误示例:static List<runnable> tasks = new ArrayList(); tasks.add(() -> System.out.println(name));</runnable> —— 这里 name 是外部类字段,Lambda 持有对外部实例的隐式引用。
- 改用静态内部类 + 显式传参:避免隐式
this捕获 - 用
WeakReference包装外部对象引用,而不是直接存 this - 静态集合必须配套清理机制:比如加定时任务调用
clear(),或用ConcurrentHashMap配合computeIfPresent做条件清除 - 检查
ThreadLocal:它底层用ThreadLocalMap(基于Entry extends WeakReference<threadlocal>></threadlocal>),但 value 是强引用——若 value 是大对象且 ThreadLocal 未remove(),就会泄露
用 WeakHashMap 就万事大吉?
不。它只对 key 做弱引用,value 仍是强引用。如果 value 很大(比如一个含上百个子对象的树形结构),key 被回收后,value 还会继续占用内存,直到下一次 GC 且没有其他引用指向它。
更麻烦的是:WeakHashMap 的 entry 清理发生在 get()、put()、size() 等方法中,靠的是内部调用 expungeStaleEntries();如果你只 put、从不 get,stale entry 就一直堆在 table 里,table 数组本身不会缩容,entry 对象也持续占内存。
- 别把它当「自动垃圾收集缓存」用,适合做元数据映射(如
Class → Annotation),不适合存业务实体 - 需要带过期策略的缓存,请用
Caffeine或Guava Cache,它们对 key/value 引用类型、淘汰时机、异步清理都有精细控制 - 若坚持手写,至少在每次
put()后主动调用一次size()触发清理,或定期new WeakHashMap(oldMap)重建
list.clear()。










