首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >JVM 本地内存跟踪(Native Memory Tracking in JVM)

JVM 本地内存跟踪(Native Memory Tracking in JVM)

作者头像
崔认知
发布2025-07-31 15:17:00
发布2025-07-31 15:17:00
15900
代码可运行
举报
文章被收录于专栏:nobodynobody
运行总次数:0
代码可运行

翻译:https://www.baeldung.com/native-memory-tracking-in-jvm

1. 概述

你是否曾疑惑:为什么 Java 进程实际占用的内存,总是明显超过通过熟悉的 -Xms-Xmx 指定的堆大小?出于多种原因和可能的优化,JVM 会在堆外再向操作系统申请额外 “本地内存”(native memory.)。这些额外申请最终可能使进程占用的内存突破 -Xmx 的上限。

本教程将:

  1. 枚举 JVM 中常见的本地内存来源,并给出对应的调优参数;
  2. 演示如何使用 Native Memory Tracking(NMT) 工具实时监控这些内存。

2. 本地内存的构成(Native Allocations)

虽然 Java 堆通常是占用最大的一块,但 JVM 还会从本地内存中划出相当可观的区域,用来存放类元数据、JIT 生成的代码、内部数据结构等。下面逐一说明。

2.1 Metaspace(元空间)

为了保存已加载类的元数据,JVM 专门开辟了一块 非堆区域 Metaspace

  • Java 8 之前叫 PermGen(永久代)
  • Metaspace 不受 -Xms/-Xmx 控制,需用下列参数单独限制: -XX:MetaspaceSize-XX:MaxMetaspaceSize (Java 8 之前是 -XX:PermSize-XX:MaxPermSize

2.2 线程

每个线程创建时都会伴随一个栈,用于局部变量和方法调用。

  • 现代 64 位系统默认约 1 MB,可通过 -Xss 调整。
  • 若线程数无上限,则栈总内存也无上限。
  • JVM 自身还需若干后台线程(GC、JIT 等)。

2.3 Code Cache(代码缓存)

JIT 把字节码编译为机器码后,放在一块叫做 Code Cache 的非堆区域。 通过 -XX:InitialCodeCacheSize-XX:ReservedCodeCacheSize 设定初始值与最大值。

2.4 垃圾收集器(Garbage Collection)

各种 GC 算法都需要额外的本地数据结构,因此会再向操作系统申请内存。不同 GC 实现差异较大。

2.5 符号(Symbols)

  • String Table(字符串表): JVM 通过 intern 机制去重字符串,所有被驻留的字符串放在一张固定大小的哈希表里; 表大小由 -XX:StringTableSize 指定。
  • Runtime Constant Pool(运行时常量池): 存放编译期生成的字面量、方法字段引用等。

2.6 本地直接缓冲区(Native Byte Buffers)

除 JVM 自身外,开发者也可通过 JNI 的 malloc 或 NIO 的 DirectByteBuffer 直接申请本地内存。

2.7 相关调优参数速查(Additional Tuning Flags)

要列出与某概念相关的所有 -XX 参数,可执行:

代码语言:javascript
代码运行次数:0
运行
复制
$ java -XX:+PrintFlagsFinal -version | grep <关键字>

例如查询 Metaspace:

代码语言:javascript
代码运行次数:0
运行
复制
$ java -XX:+PrintFlagsFinal -version | grep Metaspace

3. 使用 Native Memory Tracking(NMT)

3.0 启用 NMT

通过启动参数打开 NMT:

代码语言:javascript
代码运行次数:0
运行
复制
-XX:NativeMemoryTracking=off|summary|detail

示例:

代码语言:javascript
代码运行次数:0
运行
复制
$ java -XX:NativeMemoryTracking=summary -Xms300m -Xmx300m -XX:+UseG1GC -jar app.jar

3.1 生成即时快照( Instant Snapshots)

  1. 找到 JVM 进程 PID:
代码语言:javascript
代码运行次数:0
运行
复制
$ jps -l
7858 app.jar
  1. 打印内存信息:
代码语言:javascript
代码运行次数:0
运行
复制
$ jcmd 7858 VM.native_memory

3.2 总览(Total Allocations)

输出开头给出保留(reserved)与已提交(committed)内存:

代码语言:javascript
代码运行次数:0
运行
复制
Native Memory Tracking:
Total: reserved=1731124KB, committed=448152KB
  • reserved:JVM 向操作系统“预订”的总量。
  • committed:实际已分配、正在使用的量。

可以看到,虽然堆只有 300 MB,但进程保留了近 1.7 GB,已提交约 440 MB。

3.3 各区域详情

Heap(堆)
代码语言:javascript
代码运行次数:0
运行
复制
Java Heap (reserved=307200KB, committed=307200KB)
          (mmap: reserved=307200KB, committed=307200KB)

完全符合 -Xms300m -Xmx300m 的设置。

Class(Metaspace)
代码语言:javascript
代码运行次数:0
运行
复制
Class (reserved=1091407KB, committed=45815KB)
      (classes #6566)
      (malloc=10063KB #8519)
      (mmap: reserved=1081344KB, committed=35752KB)

约 1 GB 保留、45 MB 已提交,用于加载 6566 个类。

Thread
代码语言:javascript
代码运行次数:0
运行
复制
Thread (reserved=37018KB, committed=37018KB)
       (thread #37)
       (stack: reserved=36864KB, committed=36864KB)
       (malloc=112KB #190)
       (arena=42KB #72)

37 个线程,每个栈约 1 MB,总计 36 MB 左右。

Code Cache
代码语言:javascript
代码运行次数:0
运行
复制
Code (reserved=251549KB, committed=14169KB)
     (malloc=1949KB #3424)
     (mmap: reserved=249600KB, committed=12220KB)

当前缓存了约 13 MB 机器码,上限可达 245 MB。

GC

以 G1 为例:

代码语言:javascript
代码运行次数:0
运行
复制
GC (reserved=61771KB, committed=61771KB)
   (malloc=17603KB #4501)
   (mmap: reserved=44168KB, committed=44168KB)

约 60 MB 用于 G1 结构。

若改用 Serial GC:

代码语言:javascript
代码运行次数:0
运行
复制
GC (reserved=1034KB, committed=1034KB)
   (malloc=26KB #158)
   (mmap: reserved=1008KB, committed=1008KB)

仅 1 MB,但带来的 Stop-The-World 风险需自行权衡。

Symbol
代码语言:javascript
代码运行次数:0
运行
复制
Symbol (reserved=10148KB, committed=10148KB)
       (malloc=7295KB #66194)
       (arena=2853KB #1)

约 10 MB 用于字符串表与常量池。

3.9 随时间对比(NMT Over Time)

  1. 建立基线:
代码语言:javascript
代码运行次数:0
运行
复制
$ jcmd <pid> VM.native_memory baseline
  1. 一段时间后查看差异:
代码语言:javascript
代码运行次数:0
运行
复制
$ jcmd <pid> VM.native_memory summary.diff

输出示例:

代码语言:javascript
代码运行次数:0
运行
复制
Total: reserved=1771487KB +3373KB, committed=491491KB +6873KB
...

+ 表示增加,- 表示减少,便于快速定位泄漏或膨胀。

3.10 详细模式

如需更细粒度的地址映射,可启动时改为:

代码语言:javascript
代码运行次数:0
运行
复制
-XX:NativeMemoryTracking=detail

4. 结论

本文梳理了 JVM 本地内存的主要组成部分及其调优参数,并示范了如何利用 NMT 进行在线监控。借助这些信息,我们可以更精准地调优应用、合理规划容器或物理机的内存配额。

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

本文分享自 认知科技技术团队 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 概述
  • 2. 本地内存的构成(Native Allocations)
    • 2.1 Metaspace(元空间)
    • 2.2 线程
    • 2.3 Code Cache(代码缓存)
    • 2.4 垃圾收集器(Garbage Collection)
    • 2.5 符号(Symbols)
    • 2.6 本地直接缓冲区(Native Byte Buffers)
    • 2.7 相关调优参数速查(Additional Tuning Flags)
  • 3. 使用 Native Memory Tracking(NMT)
    • 3.0 启用 NMT
    • 3.1 生成即时快照( Instant Snapshots)
    • 3.2 总览(Total Allocations)
    • 3.3 各区域详情
      • Heap(堆)
      • Class(Metaspace)
      • Thread
      • Code Cache
      • GC
      • Symbol
    • 3.9 随时间对比(NMT Over Time)
    • 3.10 详细模式
  • 4. 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档