spring boot内存泄漏排查四步法:一查gc日志看老年代持续上涨与full gc无效;二抓heap dump用mat分析支配树和gc roots路径;三盯spring特有风险点如静态缓存、threadlocal未清理、监听器未注销;四用arthas profiler定位高频对象分配热点。

Spring Boot 应用内存占用过高或出现缓慢泄漏,通常不是“突然爆掉”,而是表现为老年代持续上涨、GC 回收效果变差、Full GC 频次增加,最终触发 java.lang.OutOfMemoryError: Java heap space。关键不是加堆内存,而是定位“谁在长期持有不该持有的对象”。下面分四步直击核心。
一、从 JVM 日志快速确认是否真有泄漏
先别急着开 MAT 或 JProfiler——先看 GC 日志是否已暴露异常:
- 启用基础诊断参数启动应用:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/myapp/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/myapp/dumps/ - 重点关注日志中三项:
• 老年代(Old Gen)使用率是否逐次上升、Full GC 后几乎不下降;
• 是否出现 “GC overhead limit exceeded”;
• 每次 GC 回收的内存字节数是否越来越小(例如从回收 200MB 降到仅 5MB)。 - 若发现老年代占用率从 40% → 60% → 85% → 98% 持续爬升,且 Full GC 后仍卡在 90%+,基本可判定存在内存泄漏。
二、抓取并分析堆快照(Heap Dump)
当内存明显异常增长(比如连续 10 分钟内堆使用量涨了 500MB),不用等 OOM,主动导出快照:
将 Spring Boot 2.7 项目升级到 Spring Boot 3.5 的实战流程,覆盖版本基线、依赖坐标替换、Jakarta 迁移、配置兼容、异步上下文传递改造与验证门禁。用于企业多模块 Maven 项目升级与排障。
- 用
jmap -dump:format=b,file=heap.hprof <pid></pid>获取当前堆镜像;
(注意:生产环境慎用,建议选低峰期,或改用jcmd <pid> VM.native_memory summary</pid>先看整体) - 用 Eclipse MAT(Memory Analyzer Tool)打开,点开 “Leak Suspects Report” —— 它会自动标出最可疑的几组对象;
- 重点看:
• 占用内存 Top 3 的类及其实例数(比如byte[]、char[]、ConcurrentHashMap$Node异常多);
• “Dominator Tree” 中哪些对象被静态变量、ThreadLocal 或单例 Bean 持有;
• 右键某个大对象 → “Path to GC Roots” → 看它为什么没被回收(常见路径:static → CacheManager → HashMap → value)。
三、盯紧 Spring Boot 特有的高危场景
很多泄漏不是代码写错,而是对 Spring 生命周期和组件机制理解偏差:
-
静态集合 + 未清理缓存:
避免private static final Map<string object> cache = new HashMap()</string>;改用@Cacheable(Caffeine/Redis)并设maximumSize和expireAfterWrite; -
ThreadLocal 泄漏:
尤其在异步线程池(@Async)或 Filter 中使用后未调用remove();检查所有ThreadLocal>是否在 finally 块中清理; -
监听器/回调未注销:
如注册了ApplicationRunner、SmartLifecycle或事件监听器(@EventListener),但未实现销毁逻辑; -
未关闭的资源引用:
手动 new 的InputStream、ZipInputStream、ObjectMapper实例被塞进静态字段;JDBC 连接虽由 HikariCP 管理,但自定义的Connection或Statement必须显式 close; -
ClassLoader 泄漏(热部署/灰度场景):
频繁 reload 应用时,旧的WebappClassLoader被新类加载器引用链持住,导致元空间或堆内 Class 对象堆积。
四、用 Arthas 快速定位“谁在疯狂 new”
如果泄漏速度较快(比如每分钟涨 100MB),可跳过 dump 分析,直接观测实时分配热点:
- 用
arthas-boot.jarattach 到进程; - 执行:
profiler start --event alloc(开始采样对象分配)profiler stop(运行 30–60 秒后停止)profiler report(生成火焰图) - 火焰图中宽度最大的方法,就是对象创建最密集的位置;
例如看到com.example.service.UserService::buildReportData下大量new byte[8192],就说明该方法在循环中反复分配大数组,且未复用或及时释放。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










