定位静态集合类内存泄漏需先用jstat观察老年代持续增长及YGC频次升高,再用jmap导出堆快照,通过MAT分析Dominator Tree和Path to GC Roots确认static field持有,最后修复时应替换为带淘汰策略的缓存库或确保显式清理。

定位静态集合类无限膨胀,关键不是一上来就翻代码,而是用 JVM 工具验证现象、抓取快照、再结合引用链确认根因。整个过程重在“证据链闭环”:从 GC 行为异常 → 堆中大对象聚集 → 静态字段强持有 → 代码中无清理逻辑。
看老年代是否“只增不减”
这是最直接的信号。运行以下命令持续观察:
-
jstat -gcutil
1000 10 (每秒输出一次,共10次) - 重点关注 O 列(老年代使用率):若从 50% 持续升到 85% 以上,且每次 Full GC 后仅下降 2%~5%,基本可锁定长生命周期对象堆积
- 同步关注 YGC 频次是否升高:说明大量对象没活过年轻代就晋升了,很可能是被静态集合长期持有着
导出并分析堆快照
确认异常后,立即低风险抓取存活对象快照:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 用 jmap -dump:live,format=b,file=heap.hprof
(加 live 参数,避免触发额外 Full GC) - 用 Eclipse MAT 打开,先看 Dominator Tree,按 Retained Heap 排序,找前几位的 ArrayList、HashMap 或你项目里常见的工具类/缓存类
- 右键可疑集合 → Path to GC Roots → 勾选 “exclude all weak/soft references”
- 若路径终点是 java.lang.Class → static field,就坐实了:这个静态字段就是泄漏根因
快速扫描高危代码模式
在工程中全局搜索以下关键词,重点检查三类写法:
立即学习“Java免费学习笔记(深入)”;
- private static.*List、private static.*Map、static.*cache
- 无上限无清理的裸集合:private static List
cache = new ArrayList(); —— 只有 add,没有 clear/remove/expiry - 伪缓存:@Service 类里的 static Map,误当轻量缓存,但既无容量限制也无过期机制
- ThreadLocal + static 组合:private static final ThreadLocal —— 实际等效于多个线程各自持有一个永不释放的强引用集合
修复要断引用链,不止清内容
list.clear() 不解决问题,关键在于打破 GC Root 引用链:
- 优先替换为专业缓存库:Caffeine(推荐)或 Guava Cache,配置 maximumSize 和 expireAfterWrite,自动淘汰
- 若必须手写,用 ConcurrentHashMap 替代 HashMap,配合定时清理任务或 LRU 策略
- 避免 static + ThreadLocal 组合;如需线程局部存储,确保在业务结束时调用 holder.remove()
- 所有静态集合的写入点,都应配套明确的清理入口,比如监听应用关闭事件、注册定时任务、或在业务流程出口显式回收

















