
Native Memory Tracking (NMT) 是Hotspot VM用来分析VM内部内存使用情况的一个功能。我们可以利用jcmd(jdk自带)这个工具来访问NMT的数据。
NMT必须先通过VM启动参数中打开,不过要注意的是,打开NMT会带来5%-10%的性能损耗。
-XX:NativeMemoryTracking=[off | summary | detail]
# off: 默认关闭
# summary: 只统计各个分类的内存使用情况.
# detail: Collect memory usage by individual call sites.使用命令导出内存情况:
jcmd pid VM.native_memory detail scale=MB > native.txt输出:
Native Memory Tracking:
Total: reserved=5848MB, committed=758MB
- Java Heap (reserved=4096MB, committed=370MB)
(mmap: reserved=4096MB, committed=370MB)
- Class (reserved=1138MB, committed=126MB)
(classes #19668)
( instance classes #18498, array classes #1170)
(malloc=6MB #82412)
(mmap: reserved=1132MB, committed=120MB)
( Metadata: )
( reserved=108MB, committed=106MB)
( used=104MB)
( free=3MB)
( waste=0MB =0.00%)
( Class space:)
( reserved=1024MB, committed=14MB)
( used=13MB)
( free=1MB)
( waste=0MB =0.00%)
- Thread (reserved=116MB, committed=116MB)
(thread #115)
(stack: reserved=115MB, committed=115MB)
- Code (reserved=244MB, committed=30MB)
(malloc=2MB #12057)
(mmap: reserved=242MB, committed=27MB)
- GC (reserved=206MB, committed=68MB)
(malloc=21MB #26698)
(mmap: reserved=185MB, committed=47MB)
- Internal (reserved=5MB, committed=5MB)
(malloc=5MB #81853)
- Other (reserved=7MB, committed=7MB)
(malloc=7MB #85)
- Symbol (reserved=27MB, committed=27MB)
(malloc=24MB #308092)
(arena=3MB #1)
- Native Memory Tracking (reserved=8MB, committed=8MB)
(tracking overhead=8MB)
- Module (reserved=1MB, committed=1MB)
(malloc=1MB #3752)
Virtual memory map:Total: reserved=5848MB, committed=758MB参数含义:
参数 | 值 | 含义 |
|---|---|---|
reserved | 5848MB | 预留内存:JVM 向操作系统申请的虚拟内存地址空间总量 |
committed | 758MB | 已提交内存:JVM 实际从操作系统获得的物理内存总量 |
关键概念:
•
Reserved(预留):JVM 向操作系统"预订"的内存地址空间,但不一定实际占用物理内存
•类似于"订座位",座位被保留了,但还没坐上去•这部分内存在虚拟地址空间中被标记为 JVM 所有,其他进程无法使用
•
Committed(已提交):JVM 实际从操作系统获得的物理内存
•类似于"真正坐下",实际占用了物理内存•这部分内存会占用真实的 RAM 或 Swap
为什么 reserved > committed?
•JVM 预先保留大块连续的虚拟地址空间(如 4GB 堆内存)•但实际只提交(使用)其中一部分物理内存(如 370MB)•随着应用运行,committed 会逐渐增长,但不会超过 reserved
Java Heap (reserved=4096MB, committed=370MB)
(mmap: reserved=4096MB, committed=370MB)含义:
•作用:存储 Java 对象实例•reserved=4096MB:预留了 4GB 的堆内存空间(通过 -Xmx4g 设置)•committed=370MB:实际使用了 370MB 物理内存•mmap:通过内存映射(memory mapping)方式分配
分析:
•✅ 堆内存使用率:370MB / 4096MB = 9%(很低,正常)•✅ 还有大量空间可用于对象分配•💡 如果 committed 接近 reserved,说明堆内存快满了
Class (reserved=1138MB, committed=126MB)
(classes #19668)
( instance classes #18498, array classes #1170)
(malloc=6MB #82412)
(mmap: reserved=1132MB, committed=120MB)
( Metadata: )
( reserved=108MB, committed=106MB)
( used=104MB)
( free=3MB)
( waste=0MB =0.00%)
( Class space:)
( reserved=1024MB, committed=14MB)
( used=13MB)
( free=1MB)
( waste=0MB =0.00%)含义:
参数 | 值 | 含义 |
|---|---|---|
classes #19668 | 19668 | 已加载的类总数 |
instance classes | 18498 | 实例类数量(普通类) |
array classes | 1170 | 数组类数量 |
malloc | 6MB | 通过 malloc 分配的内存 |
mmap | 1132MB reserved, 120MB committed | 通过内存映射分配的内存 |
Metadata(元数据空间):
•reserved=108MB:预留的元数据空间•committed=106MB:已提交的元数据空间•used=104MB:实际使用的元数据空间•free=3MB:空闲的元数据空间•waste=0MB:浪费的空间(碎片)
Class space(类空间):
•reserved=1024MB:预留的类空间(通过 -XX:CompressedClassSpaceSize=1g 设置)•committed=14MB:已提交的类空间•used=13MB:实际使用的类空间•free=1MB:空闲的类空间
分析:
•✅ 加载了 19668 个类(包括应用类和框架类)•✅ Metadata 使用率:104MB / 108MB = 96%(接近满)•⚠️ 警告:Metadata 空间快满了,可能需要增加 -XX:MetaspaceSize•✅ Class space 使用率:13MB / 1024MB = 1.3%(很低)
Thread (reserved=116MB, committed=116MB)
(thread #115)
(stack: reserved=115MB, committed=115MB)含义:
参数 | 值 | 含义 |
|---|---|---|
thread #115 | 115 | 当前活跃的线程数量 |
reserved=116MB | 116MB | 线程栈预留的内存 |
committed=116MB | 116MB | 线程栈已提交的内存 |
stack | 115MB | 线程栈占用的内存 |
计算:
•每个线程栈大小 = 116MB / 115 = 约 1MB•这是默认的线程栈大小(可通过 -Xss 调整)
分析:
•✅ 115 个线程(包括应用线程、GC 线程、JVM 内部线程)•✅ 线程数量正常(Spring Boot 应用通常有 50-200 个线程)•💡 如果线程数过多(> 500),可能存在线程泄漏
Code (reserved=244MB, committed=30MB)
(malloc=2MB #12057)
(mmap: reserved=242MB, committed=27MB)含义:
•作用:存储 JIT 编译后的本地代码(机器码)•reserved=244MB:预留的代码缓存空间•committed=30MB:已提交的代码缓存空间•malloc=2MB:通过 malloc 分配的内存•mmap=242MB reserved, 27MB committed:通过内存映射分配的内存
分析:
•✅ Code Cache 使用率:30MB / 244MB = 12.3%(正常)•✅ JIT 编译器正在工作,将热点代码编译为本地代码•💡 如果 committed 接近 reserved,可能需要增加 -XX:ReservedCodeCacheSize
GC (reserved=206MB, committed=68MB)
(malloc=21MB #26698)
(mmap: reserved=185MB, committed=47MB)含义:
•作用:GC 相关的数据结构(如 Card Table、Remembered Set 等)•reserved=206MB:预留的 GC 内存•committed=68MB:已提交的 GC 内存•malloc=21MB:通过 malloc 分配的内存•mmap=185MB reserved, 47MB committed:通过内存映射分配的内存
分析:
•✅ GC 使用率:68MB / 206MB = 33%(正常)•✅ 使用 G1 GC(从虚拟内存映射中可以看到 G1CollectedHeap)•💡 GC 内存占用与堆大小成正比
Internal (reserved=5MB, committed=5MB)
(malloc=5MB #81853)含义:
•作用:JVM 内部使用的内存(如命令行解析、性能数据等)•reserved=5MB:预留的内部内存•committed=5MB:已提交的内部内存•malloc=5MB:通过 malloc 分配的内存•#81853:分配了 81853 次
分析:
•✅ 内部内存占用很小(5MB)•✅ 分配次数较多(81853 次),但每次分配很小
Other (reserved=7MB, committed=7MB)
(malloc=7MB #85)含义:
•作用:其他未分类的内存•reserved=7MB:预留的其他内存•committed=7MB:已提交的其他内存•malloc=7MB:通过 malloc 分配的内存•#85:分配了 85 次
分析:
•✅ 其他内存占用很小(7MB)
Symbol (reserved=27MB, committed=27MB)
(malloc=24MB #308092)
(arena=3MB #1)含义:
•作用:存储符号表(如类名、方法名、字段名等字符串)•reserved=27MB:预留的符号表内存•committed=27MB:已提交的符号表内存•malloc=24MB:通过 malloc 分配的内存•#308092:分配了 308092 次(符号数量)•arena=3MB:通过 arena 分配的内存
分析:
•✅ 符号表占用 27MB(正常)•✅ 308092 个符号(包括所有类、方法、字段的名称)
Native Memory Tracking (reserved=8MB, committed=8MB)
(tracking overhead=8MB)含义:
•作用:NMT 自身的开销•reserved=8MB:NMT 预留的内存•committed=8MB:NMT 已提交的内存•tracking overhead=8MB:NMT 的跟踪开销
分析:
•✅ NMT 开销为 8MB(约占总内存的 1%)•💡 这是启用 NMT 的代价(-XX:NativeMemoryTracking=detail)
Module (reserved=1MB, committed=1MB)
(malloc=1MB #3752)含义:
•作用:Java 9+ 模块系统的内存•reserved=1MB:预留的模块内存•committed=1MB:已提交的模块内存•malloc=1MB:通过 malloc 分配的内存•#3752:分配了 3752 次
分析:
•✅ 模块系统占用很小(1MB)
虚拟内存映射显示了 JVM 在虚拟地址空间中分配的各个内存区域。
示例:
[0x000000010a84a000 - 0x000000011984a000] reserved 240MB for Code from
[0x0000000101b32743] ReservedSpace::initialize(...)含义:
参数 | 含义 |
|---|---|
[0x000000010a84a000 - 0x000000011984a000] | 虚拟地址范围 |
reserved 240MB | 预留了 240MB |
for Code | 用于代码缓存 |
from [0x0000000101b32743] | 分配调用栈(哪个函数分配的) |
从虚拟内存映射中可以看到以下类型的内存区域:
1.Code:代码缓存(JIT 编译后的代码)2.GC:垃圾回收相关的数据结构3.Class:类元数据和类空间
Code 区域:
[0x000000010a84a000 - 0x000000011984a000] reserved 240MB for Code
[0x000000010a84a000 - 0x000000010aaba000] committed 2MB
[0x000000010af7e000 - 0x000000010b1ee000] committed 2MB
[0x000000010b1ee000 - 0x000000010c84e000] committed 22MB含义:
•预留了 240MB 的虚拟地址空间用于代码缓存•实际提交了 2MB + 2MB + 22MB = 26MB 物理内存•随着 JIT 编译更多代码,committed 会增长
GC 区域:
[0x0000000119a1c000 - 0x000000011a21c000] reserved 8MB for GC
[0x0000000119a1c000 - 0x0000000119ad5000] committed 1MB含义:
•预留了 8MB 的虚拟地址空间用于 GC•实际提交了 1MB 物理内存•这是 G1 GC 的 Card Table 或 Remembered Set
Class 区域:
[0x0000000125325000 - 0x0000000125b25000] reserved and committed 8MB for Class含义:
•预留并提交了 8MB 用于类元数据•这是 Metaspace 的一部分
区域 | Reserved | Committed | 使用率 | 说明 |
|---|---|---|---|---|
Java Heap | 4096MB | 370MB | 9% | ✅ 正常 |
Class | 1138MB | 126MB | 11% | ⚠️ Metadata 96% |
Thread | 116MB | 116MB | 100% | ✅ 正常(115 线程) |
Code | 244MB | 30MB | 12% | ✅ 正常 |
GC | 206MB | 68MB | 33% | ✅ 正常 |
Internal | 5MB | 5MB | 100% | ✅ 正常 |
Other | 7MB | 7MB | 100% | ✅ 正常 |
Symbol | 27MB | 27MB | 100% | ✅ 正常 |
NMT | 8MB | 8MB | 100% | ✅ 正常 |
Module | 1MB | 1MB | 100% | ✅ 正常 |
总计 | 5848MB | 758MB | 13% | ✅ 正常 |
✅ 正常指标:
1.总内存使用率:758MB / 5848MB = 13%(很低)2.堆内存使用率:370MB / 4096MB = 9%(很低)3.线程数量:115 个(正常)4.类加载数量:19668 个(正常)
⚠️ 需要关注:
1.Metadata 使用率 96%
•Metadata 空间:104MB / 108MB = 96%•建议增加 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
💡 优化建议:
1.
增加 Metaspace 大小
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m2.
监控 Metadata 增长
•如果 Metadata 持续增长,可能存在类加载泄漏•检查是否有动态类生成(如 CGLIB、ASM)
3.
当前配置合理
•堆内存 4GB 足够•线程数量正常•Code Cache 足够
在 JVM 启动参数中添加:
-XX:NativeMemoryTracking=summary # 摘要模式
# 或
-XX:NativeMemoryTracking=detail # 详细模式(包含虚拟内存映射)方法 1:使用 jcmd
# 查看摘要
jcmd <pid> VM.native_memory summary
# 查看详细信息
jcmd <pid> VM.native_memory detail
# 查看基线对比
jcmd <pid> VM.native_memory baseline
jcmd <pid> VM.native_memory summary.diff方法 2:使用 Arthas
# 连接到应用
java -jar arthas-boot.jar
# 查看 NMT
vmtool --action getNativeMemoryA:
•Reserved:向操作系统"预订"的虚拟地址空间,不占用物理内存•Committed:实际从操作系统获得的物理内存,占用 RAM
A:
•应用加载了 19668 个类•Metadata 空间只有 108MB•建议增加 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize
A:
1.减少堆内存:-Xmx2g(如果应用不需要 4GB)2.减少线程栈大小:-Xss512k(默认 1MB)3.减少 Code Cache:-XX:ReservedCodeCacheSize=128m
A:
•Summary 模式:约 1-2% 性能开销•Detail 模式:约 5-10% 性能开销•生产环境建议使用 Summary 模式