Java 怎样用 jcmd 分析本地内存增长
来源:17golang原创
时间:2026-10-18 01:15:46 476浏览 收藏
Java 进程的 RSS 持续上涨,并不等于 Java 堆正在泄漏。JVM 还会使用类元数据、线程、JIT 编译代码、GC 结构以及其他本地内存;这时可以在目标主机上用 jcmd 的 VM.native_memory 读取 Native Memory Tracking(NMT)报告,再用基线差异判断增长方向。
官方地址:https://docs.oracle.com/en/java/javase/25/docs/specs/man/jcmd.html
最稳妥的排查顺序是:启动时开启 NMT,先看一次 summary,再建立 baseline;经过一段真实负载后查看 summary.diff,只有分类仍不明确时才使用 detail.diff。报告里的 reserved 是地址空间预留,committed 才是已经提交给 JVM 使用的部分,两者不能直接当成同一个指标。
先把 RSS、Java 堆和 NMT 的边界分开
操作系统看到的是进程级 RSS,Java 管理接口主要描述堆和非堆内存,而 NMT 关注的是 HotSpot JVM 自身的本地内存分类。三者观察角度不同,所以“堆没有上涨但 RSS 上涨”并不是矛盾。
NMT 的启动选项有两个常用级别:summary 按 JVM 子系统汇总,detail 进一步记录调用点和虚拟内存区域。默认值是 off,也就是说进程没有在启动时打开 NMT,之后不能只靠一条 jcmd 命令补回历史数据。
在 Linux、macOS 或其他支持的环境中,诊断端与目标 JVM 应在同一台机器上,并使用启动 JVM 时相同的有效用户和组标识。容器中还要先确认 jcmd 看到了正确的 PID。
第一步:用 summary 取得可读的全局轮廓
先用轻量的摘要报告确认目标进程和大类变化,不要一上来就抓 detail。下面的命令假设进程号是 12345,命令本身只做读取和基线设置,不代表已经在本机执行过。
# 列出当前用户可见的 Java 进程,先确认 PID 与主类 jcmd -l # 查看 JVM 当前的 NMT 摘要,重点关注各分类的 reserved 和 committed jcmd 12345 VM.native_memory summary # 在可复查的时间点保存 NMT 基线,后续 diff 会与它比较 jcmd 12345 VM.native_memory baseline
摘要通常会出现 Java Heap、Class、Thread、Code、GC、Compiler、Internal、Other 和 Native Memory Tracking 等分类。分类名称会随 JDK 发布变化,因此应先看目标 JVM 实际返回的报告,不要把某一版样例当成固定协议。

第二步:按 reserved 和 committed 判断增长属于哪一层
阅读报告时先看总量,再定位变化最大的分类。reserved 表示 JVM 为某类内存预留的地址空间,committed 表示已经提交、可被使用的内存。地址空间预留变大不一定代表物理内存同步增加;如果 RSS 与 committed 同时上涨,才更值得继续追踪。
| 观察项 | 更适合回答的问题 | 不要直接得出的结论 |
|---|---|---|
| Java Heap | 堆相关的占用是否随负载上升 | 不能代表全部进程内存 |
| Class / Metaspace 相关项 | 类加载、类元数据是否持续增长 | 不能单凭一行判断类泄漏 |
| Thread | 线程结构及相关本地内存是否增加 | 不能把它等同于线程栈上限 |
| Code / Compiler | 编译代码和编译器区域是否变化 | 不能直接推断 JIT 有故障 |
| Other 或未覆盖部分 | 是否需要转向进程级或第三方库分析 | 不能把未知项归咎于 Java 堆 |
举例说,如果 Java Heap 基本稳定,但 Thread 与 Class 持续上升,应优先检查线程创建回收、类加载器生命周期和动态生成类;如果 NMT 分类变化很小而 RSS 明显增加,则要把 JNI、直接缓冲区、内存映射文件或第三方 native 库列入下一层排查。NMT 不会替你覆盖所有第三方原生分配。
第三步:用 summary.diff 把一次增长变成可比较的证据
单次 summary 只能告诉你当前状态,无法说明“从什么时候开始增加”。建立 baseline 后,等待一段与问题相关的真实业务负载,再读取差异报告:
# 读取各 JVM 分类相对 baseline 的变化,先判断增长方向 jcmd 12345 VM.native_memory summary.diff # 如果分类级别仍无法定位,再查看调用点与虚拟内存区域的差异 jcmd 12345 VM.native_memory detail.diff
summary.diff 适合回答“哪一类增长最多”,detail.diff 适合继续回答“哪些调用点或区域贡献了增长”。对于增长很小的泄漏,观察窗口需要足够长,否则差值可能被噪声覆盖。每次采集都应记录 JVM 启动参数、PID、采集时间、负载阶段和报告原文,方便将差异与发布或流量变化对齐。

第四步:在 summary 和 detail 之间做架构选择
如果目标是长期观察生产进程,通常先考虑 summary;如果目标是短时间内定位疑似调用点,再评估 detail 的成本。Oracle 的排障资料说明,开启 NMT 会带来额外开销,detail 还会记录更多分配信息,因此不能把它当成没有代价的常驻开关。
# 只按 JVM 子系统跟踪,适合先建立分类级基线 java -XX:NativeMemoryTracking=summary -jar app.jar # 需要调用点和虚拟区域信息时使用 detail,但应评估监控成本 java -XX:NativeMemoryTracking=detail -jar app.jar
这两个选项必须在 JVM 启动时决定。若当前进程是以 off 启动,不能事后用 jcmd 让它产生过去的 NMT 轨迹;实际排查往往需要安排一次带有 NMT 的受控重启,并在重启前明确采集窗口。
第五步:把报告落到一份排查清单
- 先确认 jcmd、目标 JVM 和有效用户/组匹配,避免把连接失败误判成内存没有变化。
- 记录启动参数,确认 NMT 是
summary还是detail,并记下 JVM 版本。 - 执行一次 summary,保存总量、主要分类、reserved 和 committed。
- 建立 baseline,在同一业务负载下等待足够时间,再执行 summary.diff。
- 只有分类级结论不够时再看 detail.diff,并把调用点与发布、线程、类加载或 native 依赖变化关联起来。
- 若 RSS 增长与 NMT 变化不匹配,转向直接缓冲区、JNI、映射文件、第三方 native 库和操作系统级工具,不要继续只调大堆。
常见问题
为什么 summary 中的 committed 变大,RSS 却没有等量变大?
committed 是 JVM 已提交的内存视角,RSS 还会受到操作系统回收、共享页、映射方式和进程其他区域影响。它们应放在同一采集窗口内比较,而不是逐行做一比一换算。
为什么 detail.diff 没有找到第三方库的泄漏?
NMT 重点跟踪 HotSpot JVM 的本地内存,不是所有 JNI 或第三方 native 分配都能归类。若 NMT 稳定而 RSS 继续增长,应该把调查范围移到相应库的分配接口和操作系统级进程观测。
baseline 可以重复建立吗?
可以,但每次重建都会改变后续 diff 的比较起点。记录“谁在什么时候建立了基线”很重要,否则两份差异报告可能没有可比性。
总结
用 jcmd 分析本地内存增长的关键,不是记住一条命令,而是把进程级 RSS、JVM 分类和 NMT 差异报告分层。先用 summary 建立轮廓,再用 baseline 与 summary.diff 找增长类别,最后才用 detail.diff 追调用点;当这些结果解释不了 RSS 时,明确承认 NMT 的覆盖边界,转向第三方 native 和操作系统层面。
-
479 收藏
-
337 收藏
-
128 收藏
-
149 收藏
-
202 收藏
-
307 收藏
-
341 收藏
-
327 收藏
-
501 收藏
-
208 收藏
-
文章 · java教程 | 1小时前 | 字符集 · Java教程 · HTTP客户端 · java httpclient charset BodyHandlers ofByteArray ofString429 收藏
-
170 收藏
-
265 收藏
-
293 收藏
-
207 收藏
-
308 收藏
-
140 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习