arraylist本身不会导致内存泄漏,问题在于被静态字段、缓存或内部类长期强引用;应通过jstat观察老年代持续增长、用mat分析gc roots定位静态引用根因,并避免裸静态集合、sublist滥用及静态集合持有内部类。

ArrayList 本身不会造成内存泄漏,问题出在“谁持有它”和“怎么用它”。关键不是 ArrayList 类型,而是它被静态字段、缓存结构或内部类意外长期强引用,导致本该回收的对象一直留在堆里。
看现象:先确认是不是内存泄漏
别急着翻代码,先观察 JVM 行为:
- 用 jstat -gc
1000 5 持续采样:老年代使用量(OU)是否单向缓慢上涨,且每次 Full GC 后几乎不回落 - 年轻代 GC(YGC)频次是否异常升高——说明大量对象“逃逸”到老年代,常见于被静态 ArrayList 长期持有时
- 对比业务请求量与堆内存增长趋势:若流量平稳但堆持续上涨,基本可排除负载问题,指向对象堆积
抓快照:定位具体泄漏源
确认异常后,导出堆快照分析:
- 低峰期执行 jmap -dump:format=b,file=heap.hprof
- 用 Eclipse MAT 打开,直接查看 Leak Suspects Report,第一个嫌疑常是
java.util.ArrayList或其所属包名 - 进入 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 的视图,会隐式持有整个底层数组。只取几个 ID 却存进静态缓存,等于把全部原始数据钉在堆里
-
静态集合 + 内部类/Lambda:如
staticList.add(new Runnable() { })或staticList.add(() -> {}),会隐式持有所在外部类的this,可能意外钉住 Controller、Service 等大对象
修复与预防:断引用链,别只清内容
加个 list.clear() 不等于修复成功:
- 清除后仍需确认 MAT 中该 ArrayList 实例是否还在 GC Roots 路径上;若还在,说明还有其他强引用没断
- 优先替换为 Caffeine Cache,配置
maximumSize和expireAfterWrite,让淘汰自动发生 - 必须手写缓存时,避免
static List,改用 ConcurrentHashMap> 或 WeakHashMap(注意 key 是弱引用) - 对已扩容但未填满的 ArrayList,调用
trimToSize()可收缩elementData数组,减少冗余引用槽位
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











