堆外内存持续增长最典型的信号是进程物理内存(res)一路飙升而堆内存(-xmx)稳定;需立即用top查res与-xmx差值,启用nmt(-xx:nativememorytracking=detail)对比direct、internal、thread等区域增量,并用pmap -x找[anon]块,netty应用应开启-dio.netty.leakdetection.level=paranoid捕获泄漏堆栈。

堆外内存持续增长,最典型的信号是进程物理内存(RES)一路飙升,而堆内存(-Xmx 设置值)却很稳定。这时候别急着调大堆,得立刻转向堆外排查。
看 RES 和堆配置的差值是否异常
用 top -p
启用 NMT 看原生内存分配变化
必须提前加启动参数:-XX:NativeMemoryTracking=detail(上线前就该加上,临时加要重启)。然后执行:
-
jcmd
VM.native_memory baseline (初始快照) - 等几小时或一天后,再执行:jcmd
VM.native_memory detail.diff
重点盯这几个区域的增量:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- Direct:NIO DirectByteBuffer 分配量。增长几百MB甚至几个GB?大概率是 Netty 或自定义 NIO 代码没释放 ByteBuf
- Internal:JVM 内部结构开销,比如线程本地缓存、CodeCache 等。若增长显著,可能和大量线程或动态类加载有关
- Thread:线程数暴增或线程栈过大(比如 -Xss 设置过高)
- Metaspace:类加载过多未卸载,常见于热部署、OSGi、或反射生成大量代理类
用 pmap 定位匿名内存块
执行 pmap -x
Netty 应用要开泄漏检测开关
如果是 Netty 服务,在 JVM 参数里加上:-Dio.netty.leakDetection.level=PARANOID。它会在日志里打印出未 release 的 ByteBuf 创建堆栈,例如:
LEAK: ByteBuf.release() was not called before it's garbage-collected.
Created at: io.netty.buffer.PooledByteBufAllocator.newDirectBuffer()
顺着堆栈就能定位到哪段业务代码申请了 ByteBuf 却没调 release(),或者用了非池化缓冲区(Unpooled)但忘了释放。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










