堆外内存泄漏需通过NMT定位:先确认RSS上涨、堆监控正常、OOM Killer杀进程三现象;启动时配置-XX:NativeMemoryTracking=detail和-XX:+UnlockDiagnosticVMOptions;用jcmd抓baseline与diff快照,重点分析Internal、Thread、Metaspace、Other增长;结合DirectByteBuffer、线程池、动态类加载等代码归因。

堆外内存泄漏无法通过常规堆监控发现,必须用原生内存追踪手段定位。核心思路是:确认现象 → 启用NMT → 捕获差异 → 锁定模块 → 结合代码验证。
先确认是不是堆外泄漏
看三个关键信号:
- top 或 ps 显示 Java 进程 RSS(物理内存)持续上涨,比如从 3GB 涨到 12GB
- jstat -gc 或 JMX 显示堆内存稳定、老年代使用率低、GC 频次少、无 Full GC
- 服务被 Linux OOM Killer 杀掉,但日志里没有 java.lang.OutOfMemoryError: Java heap space
满足这三点,基本可排除堆内问题,重点转向堆外。
必须启动时开启 NMT 并选对模式
NMT(Native Memory Tracking)是 HotSpot 内置唯一可靠的堆外内存追踪工具,运行时无法开启,且参数位置和写法极敏感:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 生产长期监控用:-XX:NativeMemoryTracking=summary(开销约 1–3%,安全)
- 排查期精准定位用:-XX:NativeMemoryTracking=detail(需重启,记录调用栈)
- 必须加 -XX:+UnlockDiagnosticVMOptions 才能使用 jcmd 触发命令
- 参数要放在 java 命令最前面,不能写成 =on,也不能放在 -jar 后面,否则静默失效
用 jcmd 抓两次快照比对增长
不是只看一次 summary,而是建立基线再对比变化:
- 应用刚启动、负载平稳时执行:jcmd
VM.native_memory baseline - 过 10–30 分钟(或等 RSS 明显上涨后)执行:jcmd
VM.native_memory detail.diff - 重点关注 committed 增长大的模块,用 grep 快速筛选:grep -A5 -B5 "committed.*[0-9]M"
- 特别盯住这几类关键词:[thread](线程栈)、[class](元空间)、[direct](DirectByteBuffer)、[arena](本地内存池)、[code](JIT 缓存)
结合典型模块做代码级归因
不同增长项对应不同泄漏源头,修复不能只靠等 GC:
- [direct] 持续涨:检查 Netty、Kafka Client、自定义 NIO 逻辑是否调用 allocateDirect() 后未 clean() 或未 close();注意 try-with-resources 是否覆盖了 Buffer
- [class] 持续涨:Spring AOP、JSON 反序列化(如 Jackson 的 mixin)、Groovy 脚本、热部署场景易动态生成类;确认 ClassLoader 是否被持有、类是否能卸载
- [thread] 持续涨:查 ScheduledThreadPool、WorkStealingPool、未 shutdown 的线程池;每个线程默认占 1MB 栈空间,几百个线程就吃掉 GB 级内存
- [code] 异常膨胀:高频反射、Lambda 表达式、方法句柄触发 JIT 编译缓存打满;可临时加 -XX:ReservedCodeCacheSize=256m 限制并观察
- 其他项涨得离谱但 NMT 不显示来源:高度怀疑 JNI 或 C/C++ 库(如 JavaCV、FFmpeg、数据库驱动),需配合 pstack、perf record 或 valgrind 进一步分析
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










