直接看老年代内存走势和GC Roots引用链可快速锁定静态集合类内存泄漏:OU持续上升且FGC后不降、OC占比超85%、YGC频次升高;jmap导出堆后用MAT分析Leak Suspects,查Dominator Tree中static字段路径;重点扫描裸集合、伪缓存、ThreadLocal+static三类高危写法;修复需切断GC Root引用,优先用Caffeine等专业缓存替代。

直接看老年代内存走势和 GC Roots 引用链,就能快速锁定静态集合类引起的内存泄漏。
看现象:用 jstat 确认老年代单向增长
运行 jstat -gc <pid> 2000 每两秒刷新一次,重点关注:
- OU(Old Used)持续上升,且每次 FGC 后几乎不下降——说明对象被长期强引用,没被回收
- OC(Old Capacity)占比长期 >85% 并缓慢爬升,是典型静态集合堆积信号
- YGC 频次明显升高——年轻代对象频繁“晋升”到老年代,往往是因为被静态集合提前持有了
抓快照:用 jmap 导出堆并用 MAT 分析
确认异常后,立即执行:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- jmap -dump:format=b,file=heap.hprof <pid>(建议选业务低峰期)
- 用 Eclipse MAT 打开,点 Leak Suspects Report——90% 情况下第一个嫌疑就是你项目里的 static ArrayList 或 HashMap
- 进入 Dominator Tree,按包名排序,找到疑似缓存类或工具类;右键该集合 → Path to GC Roots → 勾选 exclude all weak/soft references
- 若路径终点显示 java.lang.Class → static field,就实锤了:这个静态字段就是泄漏根源
查代码:三类高危写法必须立刻扫描
全局搜索 private static.*List、static.*Map、static.*cache,重点检查:
立即学习“Java免费学习笔记(深入)”;
-
裸集合无上限无清理:比如
private static List<User> cache = new ArrayList<>();,只有 add,没有 clear/remove 逻辑 - 伪缓存只 put 不 expire:尤其注意 Spring @Service 类里声明的 static Map,常被误当轻量缓存用
-
ThreadLocal + static 组合:如
private static final ThreadLocal<Map> holder = new ThreadLocal<>();,实际等效于多个线程各自持有一个永不释放的强引用集合
修方案:断引用链比清内容更重要
修复不是简单调用 list.clear(),关键要切断 GC Root 引用并防复发:
-
优先替换为专业缓存库:Caffeine(推荐)或 Guava Cache,配
maximumSize和expireAfterWrite,自动淘汰 -
手写缓存需加约束:用
ConcurrentHashMap替代HashMap,配合定时任务或 LRU 策略清理过期项 -
静态集合若真需存在,必须提供显式清除入口:比如暴露
clearCache()方法,并在应用关闭钩子(Runtime.getRuntime().addShutdownHook)中调用

















