堆外内存占用过高需用nmt定位:启动加-xx:nativememorytracking=detail,再用jcmd vm.native_memory summary.diff scale=mb对比变化,重点监控class、direct、thread、internal四大区域,并结合pmap、jstack、gc日志等交叉验证。

堆外内存(Off-Heap)占用过高,不能只盯着 jstat 或堆转储,因为这些工具对非堆区域完全“看不见”。关键要锁定是 元空间、直接内存、线程栈 还是 NMT 未统计的本地映射,再用对应工具分层验证。
先确认是不是堆外真高了
别被 top 的 RES 值带偏——它包含堆+所有堆外内存。得做减法:
- 用
jstat -gc <pid></pid>算出当前堆已用大小(比如 Eden + S0 + S1 + Old 总和) - 用
pmap -x <pid> | awk '{sum += $3} END {print sum/1024 " MB"}'</pid>得到进程总物理内存(RES近似值) - 两者相减,若差值 > 500MB,就值得深挖堆外
用 NMT 定位具体堆外区域
NMT(Native Memory Tracking)是 JVM 自带的堆外内存追踪器,最权威:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 启动时必须加参数:
-XX:NativeMemoryTracking=detail(没开这个,后面全白搭) - 运行中执行:
jcmd <pid> VM.native_memory summary scale=MB</pid>,看各模块占比 - 重点盯:Class(元空间)、Internal(JVM内部结构)、Thread(线程栈总和)、Direct(ByteBuffer.allocateDirect)
- 想比变化?执行两次:
jcmd <pid> VM.native_memory summary.diff scale=MB</pid>,只显示增长项
分模块针对性排查
根据 NMT 指向的高增长区域,选下一步动作:
-
元空间(Class)暴涨:查动态类生成,如 Spring AOP 代理过多、Groovy 脚本、反复 defineClass;加
-XX:MaxMetaspaceSize=256m并观察是否 OOM,再配合jcmd <pid> VM.class_hierarchy</pid>看加载类数量 -
Direct 内存飙升:Netty、Kafka、NIO 框架常用;检查是否
ByteBuffer.allocateDirect()后没调cleaner或free();开启 Netty 泄漏检测:-Dio.netty.leakDetection.level=paranoid -
Thread 区域过大:说明线程数爆炸;用
jstack <pid> | grep 'java.lang.Thread.State' | wc -l</pid>粗略统计线程数;再用ps -T -p <pid> | wc -l</pid>看 OS 级线程数是否匹配 -
Internal 或 Unknown 显著增长:可能是 JNI 库(如 JNA、数据库驱动)或 mmap 文件未释放;用
pmap -x <pid></pid>找大块[anon]或.so映射,结合lsof -p <pid></pid>查打开文件
辅助验证:GC 日志与系统工具交叉印证
NMT 是起点,不是终点,需用其他信号交叉验证:
- 开 GC 日志:
-Xlog:gc*:file=gc.log:time,uptime,pid,tags,如果频繁出现Metadata GC Threshold,就是元空间触发回收,佐证 Class 区问题 - 用
cat /proc/<pid>/status | grep VmRSS</pid>和VmData对比,VmData 高但堆不大,大概率是 mmap 或 direct buffer - 容器环境加一层:
docker stats <container_id></container_id>看 RSS 是否同步上涨,排除宿主机干扰
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










