K8s容器中Java进程内存使用量与jcmd统计值差异排查
先统一单位明确差值:
/sys/fs/cgroup/memory/memory.usage_in_bytes:1597472768字节 ≈ 1523MBjcmd VM.native_memory summary已提交内存:857660KB ≈ 837MB
两者差值约686MB,排查方向如下:
1. 深挖NMT未统计的内存细节
默认summary模式的NMT输出不够详尽,切换到detail模式查看未归类内存:
jcmd <pid> VM.native_memory detail
重点关注Unknown分类,这部分是NMT无法追踪的内存分配——Netty若通过Unsafe.allocateMemory()而非ByteBuffer.allocateDirect()分配直接内存,会被归入此类。
2. 排查Pod内非Java进程的内存占用
Cgroup统计的是整个Pod内所有进程的内存总和,而非仅Java进程。执行以下命令确认Pod内进程:
ps aux
对每个非Java进程用pmap -x <pid>查看内存占用,排除sidecar容器、日志采集进程、init进程等的内存消耗。
3. 扣除页缓存的影响
memory.usage_in_bytes包含文件页缓存,而NMT仅统计JVM进程自身内存。查看页缓存大小:
cat /sys/fs/cgroup/memory/memory.stat | grep cache
将memory.usage_in_bytes减去该缓存值后,再与JVM内存对比,差值会明显缩小。
4. 直接查看Netty的内存池状态
Redisson依赖的Netty直接内存可能未被NMT捕获,可通过两种方式验证:
- 开启Netty内存泄漏检测:启动JVM时添加参数
-Dio.netty.leakDetection.level=advanced,日志中会输出未释放的缓冲区信息。 - 通过JMX获取Netty内存池指标:查找
io.netty.buffer.PooledByteBufAllocator相关MBean,查看usedDirectMemory等字段的实际值。
此外,用pmap -x <pid>查看Java进程的内存映射,匿名映射(anon)的大区块大概率是Netty的直接内存池。
5. 验证JVM常驻内存与Cgroup RSS的匹配度
memory.usage_in_bytes包含swap和缓存,而rss(常驻内存)更贴近进程实际物理内存占用:
- 查看Cgroup的RSS值:
cat /sys/fs/cgroup/memory/memory.stat | grep rss
- 查看JVM的常驻内存:
jcmd <pid> VM.info | grep "Resident Set Size"
两者数值应更接近,若仍有较大差值,需排查JVM未统计的内存分配(如JNI调用的第三方库内存)。
6. 排查堆外内存泄漏风险
检查Redisson连接池配置是否合理,若连接未及时释放,会导致Netty缓冲区堆积。同时添加JVM参数开启退出时的NMT统计:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics
进程退出时会输出完整的内存统计,帮助定位泄漏点。
内容的提问来源于stack exchange,提问作者Niranjan R

