ArrayList 本身不会导致内存泄漏,关键在于持有它的静态引用未释放;需通过 jstat 观察老年代增长、jmap 导出堆快照、MAT 分析 GC Roots 路径定位静态集合泄漏源,并避免裸静态 List、subList 视图滥用及内部类隐式引用。

ArrayList 本身不会造成内存泄漏,问题出在“谁持有它”和“怎么用它”。排查关键不是盯着 ArrayList 类,而是顺藤摸瓜,找到强引用链未断的源头;优化重点也不是删掉 list.clear(),而是让对象真正可被 GC 回收。
看现象:先确认是不是 ArrayList 相关泄漏
别急着改代码,先观察 JVM 行为:
- 执行 jstat -gc
持续观察:老年代使用量(OU)是否单向缓慢上涨,且每次 Full GC 后几乎不回落 - 年轻代 GC(YGC)频次是否异常升高——说明大量本该短命的对象“逃逸”到老年代,常见于被静态 ArrayList 长期持有着
- 对比堆内存增长趋势与业务请求量:若 QPS 平稳但堆持续上涨,基本可排除流量激增,指向对象堆积
抓快照:定位具体是哪个 ArrayList 在撑大堆
确认异常后,导出堆快照分析:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 低峰期执行 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 等关键词,重点关注:
立即学习“Java免费学习笔记(深入)”;
-
裸静态集合无清理:比如
private static List<user> cache = new ArrayList();</user>—— 只 add,从不 clear/remove/expiry - subList 视图滥用:它不是新数组,而是原 ArrayList 的视图,会隐式持有整个底层数组引用;只取几个元素却存进静态缓存,等于把全部数据钉在堆里
-
静态集合 + 内部类/Lambda:如
staticList.add(new Runnable() { })或staticList.add(() -> {}),会隐式持有所在外部类的 this,可能意外钉住 Controller、Service 等大对象
验证修复:清内容不等于断引用
加个 list.clear() 不代表修复成功:
- 清除后仍需确认 MAT 中该 ArrayList 实例是否还在 GC Roots 路径上;若还在,说明还有其他强引用没断
- 优先替换为 Caffeine Cache,配置
maximumSize和expireAfterWrite,让淘汰自动发生 - 必须手写缓存时,避免
static List,改用 ConcurrentHashMap> 或定时清理策略

















