arraylist 本身不会导致内存泄漏,但静态持有、sublist 视图滥用或内部类隐式引用等不当使用会使其长期强引用对象。排查需先用 jstat 观察老年代持续增长和 ygc 升高,再用 jmap + mat 定位静态字段根因,最后替换为 caffeine 或弱引用方案。

ArrayList 本身不会导致内存泄漏,但不当使用会让对象长期被强引用持有,无法被 GC 回收。排查重点不是看 ArrayList 本身,而是查“谁在用它、怎么用、用完清没清”。
看现象:先确认是不是 ArrayList 相关的泄漏
别一上来就翻代码,先观察 JVM 行为:
- 用 jstat -gc
持续观察:老年代使用量(OU)是否单向缓慢上涨,且每次 Full GC 后几乎不下降 - 年轻代 GC(YGC)频次是否异常升高——说明大量对象“逃逸”到老年代,常见原因是被静态 ArrayList 长期持有着
- 对比堆内存增长趋势和业务请求量:若请求量平稳但堆持续涨,基本可排除流量问题,指向对象堆积
抓快照:定位具体是哪个 ArrayList 在撑大堆
确认异常后,导出堆快照分析:
- 执行 jmap -dump:format=b,file=heap.hprof
(建议低峰期操作) - 用 Eclipse MAT 打开,直接点 Leak Suspects Report,通常第一个嫌疑就是 java.util.ArrayList 或 HashMap
- 进入 Dominator Tree,按你项目包名排序,找到可疑的静态 List;右键 → Path to GC Roots → 勾选 “exclude all weak/soft references”
- 如果路径终点是 java.lang.Class → static field,就坐实了:这个静态字段就是泄漏根因
查代码:盯死三类高危写法
全局搜索 private static.*List、static.*cache 等关键词,重点关注:
-
裸静态集合无清理:比如
private static List<user> cache = new ArrayList();</user>—— 只有 add,从无 clear/remove/expiry - subList 返回值被长期持有:它不是新数组,而是原 ArrayList 的视图,会隐式持有整个底层数组引用;若只取几个元素却存进静态缓存,等于把全部数据钉在堆里
-
静态集合 + 内部类/Lambda:如
staticList.add(new Runnable() { ... })或staticList.add(() -> {...}),会隐式持有所在外部类的 this,可能意外钉住 Activity、Controller 等大对象
验证修复:别只清内容,要断引用链
加个 list.clear() 不等于修复成功:
- 清除后仍需确认 MAT 中该 ArrayList 实例是否还在 GC Roots 路径上;若还在,说明还有其他强引用没断
- 优先替换为 Caffeine Cache,配置
maximumSize和expireAfterWrite,让淘汰自动发生 - 必须手写缓存时,避免 static List,改用 ConcurrentHashMap
> ,或至少确保添加前检查容量并主动 trimToSize() - 对分批处理场景,确保每批用完即弃:声明为局部变量,处理完离开作用域,不跨批次复用
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











