首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Java内存分析利器:Native Memory Tracking

Java内存分析利器:Native Memory Tracking

作者头像
码农戏码
发布2026-06-25 19:59:15
发布2026-06-25 19:59:15
940
举报

Native Memory Tracking (NMT) 是Hotspot VM用来分析VM内部内存使用情况的一个功能。我们可以利用jcmd(jdk自带)这个工具来访问NMT的数据。

NMT必须先通过VM启动参数中打开,不过要注意的是,打开NMT会带来5%-10%的性能损耗。

代码语言:javascript
复制
-XX:NativeMemoryTracking=[off | summary | detail]
# off: 默认关闭
# summary: 只统计各个分类的内存使用情况.
# detail: Collect memory usage by individual call sites.

使用命令导出内存情况:

代码语言:javascript
复制
jcmd pid VM.native_memory detail scale=MB > native.txt

输出:

代码语言:javascript
复制
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:

一、总览(Summary)

1.1 总体内存使用

代码语言:javascript
复制
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


二、各内存区域详解

2.1 Java Heap(Java 堆)

代码语言:javascript
复制
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,说明堆内存快满了


2.2 Class(类元数据)

代码语言:javascript
复制
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%(很低)


2.3 Thread(线程)

代码语言:javascript
复制
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),可能存在线程泄漏


2.4 Code(代码缓存)

代码语言:javascript
复制
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


2.5 GC(垃圾回收)

代码语言:javascript
复制
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 内存占用与堆大小成正比


2.6 Internal(内部)

代码语言:javascript
复制
Internal (reserved=5MB, committed=5MB)
    (malloc=5MB #81853)

含义:

作用:JVM 内部使用的内存(如命令行解析、性能数据等)•reserved=5MB:预留的内部内存•committed=5MB:已提交的内部内存•malloc=5MB:通过 malloc 分配的内存•#81853:分配了 81853 次

分析:

•✅ 内部内存占用很小(5MB)•✅ 分配次数较多(81853 次),但每次分配很小


2.7 Other(其他)

代码语言:javascript
复制
Other (reserved=7MB, committed=7MB)
    (malloc=7MB #85)

含义:

作用:其他未分类的内存•reserved=7MB:预留的其他内存•committed=7MB:已提交的其他内存•malloc=7MB:通过 malloc 分配的内存•#85:分配了 85 次

分析:

•✅ 其他内存占用很小(7MB)


2.8 Symbol(符号表)

代码语言:javascript
复制
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 个符号(包括所有类、方法、字段的名称)


2.9 Native Memory Tracking(NMT 自身)

代码语言:javascript
复制
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


2.10 Module(模块)

代码语言:javascript
复制
Module (reserved=1MB, committed=1MB)
    (malloc=1MB #3752)

含义:

作用:Java 9+ 模块系统的内存•reserved=1MB:预留的模块内存•committed=1MB:已提交的模块内存•malloc=1MB:通过 malloc 分配的内存•#3752:分配了 3752 次

分析:

•✅ 模块系统占用很小(1MB)


三、虚拟内存映射(Virtual Memory Map)

3.1 什么是虚拟内存映射?

虚拟内存映射显示了 JVM 在虚拟地址空间中分配的各个内存区域。

示例:

代码语言:javascript
复制
[0x000000010a84a000 - 0x000000011984a000] reserved 240MB for Code from
    [0x0000000101b32743] ReservedSpace::initialize(...)

含义:

参数

含义

[0x000000010a84a000 - 0x000000011984a000]

虚拟地址范围

reserved 240MB

预留了 240MB

for Code

用于代码缓存

from [0x0000000101b32743]

分配调用栈(哪个函数分配的)

3.2 内存区域类型

从虚拟内存映射中可以看到以下类型的内存区域:

1.Code:代码缓存(JIT 编译后的代码)2.GC:垃圾回收相关的数据结构3.Class:类元数据和类空间

3.3 示例分析

Code 区域:

代码语言:javascript
复制
[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 区域:

代码语言:javascript
复制
[0x0000000119a1c000 - 0x000000011a21c000] reserved 8MB for GC
    [0x0000000119a1c000 - 0x0000000119ad5000] committed 1MB

含义:

•预留了 8MB 的虚拟地址空间用于 GC•实际提交了 1MB 物理内存•这是 G1 GC 的 Card Table 或 Remembered Set

Class 区域:

代码语言:javascript
复制
[0x0000000125325000 - 0x0000000125b25000] reserved and committed 8MB for Class

含义:

•预留并提交了 8MB 用于类元数据•这是 Metaspace 的一部分


四、内存使用总结

4.1 内存分布

区域

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%

✅ 正常

4.2 关键发现

✅ 正常指标:

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 大小

代码语言:javascript
复制
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m

2.

监控 Metadata 增长

•如果 Metadata 持续增长,可能存在类加载泄漏•检查是否有动态类生成(如 CGLIB、ASM)

3.

当前配置合理

•堆内存 4GB 足够•线程数量正常•Code Cache 足够


五、如何获取 NMT 报告

5.1 启用 NMT

在 JVM 启动参数中添加:

代码语言:javascript
复制
-XX:NativeMemoryTracking=summary  # 摘要模式
# 或
-XX:NativeMemoryTracking=detail   # 详细模式(包含虚拟内存映射)

5.2 查看 NMT 报告

方法 1:使用 jcmd

代码语言:javascript
复制
# 查看摘要
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

代码语言:javascript
复制
# 连接到应用
java -jar arthas-boot.jar

# 查看 NMT
vmtool --action getNativeMemory

六、常见问题

Q1: Reserved 和 Committed 的区别?

A:

Reserved:向操作系统"预订"的虚拟地址空间,不占用物理内存•Committed:实际从操作系统获得的物理内存,占用 RAM

Q2: 为什么 Metadata 使用率这么高?

A:

•应用加载了 19668 个类•Metadata 空间只有 108MB•建议增加 -XX:MetaspaceSize-XX:MaxMetaspaceSize

Q3: 如何减少内存占用?

A:

1.减少堆内存:-Xmx2g(如果应用不需要 4GB)2.减少线程栈大小:-Xss512k(默认 1MB)3.减少 Code Cache:-XX:ReservedCodeCacheSize=128m

Q4: NMT 的性能开销是多少?

A:

•Summary 模式:约 1-2% 性能开销•Detail 模式:约 5-10% 性能开销•生产环境建议使用 Summary 模式


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农戏码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、总览(Summary)
    • 1.1 总体内存使用
  • 二、各内存区域详解
    • 2.1 Java Heap(Java 堆)
    • 2.2 Class(类元数据)
    • 2.3 Thread(线程)
    • 2.4 Code(代码缓存)
    • 2.5 GC(垃圾回收)
    • 2.6 Internal(内部)
    • 2.7 Other(其他)
    • 2.8 Symbol(符号表)
    • 2.9 Native Memory Tracking(NMT 自身)
    • 2.10 Module(模块)
  • 三、虚拟内存映射(Virtual Memory Map)
    • 3.1 什么是虚拟内存映射?
    • 3.2 内存区域类型
    • 3.3 示例分析
  • 四、内存使用总结
    • 4.1 内存分布
    • 4.2 关键发现
  • 五、如何获取 NMT 报告
    • 5.1 启用 NMT
    • 5.2 查看 NMT 报告
  • 六、常见问题
    • Q1: Reserved 和 Committed 的区别?
    • Q2: 为什么 Metadata 使用率这么高?
    • Q3: 如何减少内存占用?
    • Q4: NMT 的性能开销是多少?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档