当前位置: 首页 > article >正文

Native Memory Tracking 与 RSS的差异问题

一 问题现象

前一段时间用nmt查看jvm进程的栈区占用的内存大小。测试代码如下

public class ThreadOOM {

    public static void main(String[] args) {
        int i = 1;
        while (i < 3000) {
            Thread thread = new TestThread();
            thread.start();
            System.out.println("thread : " + i);
            i++;
        }
    }
}


class TestThread extends Thread {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(Long.MAX_VALUE);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

启动命令

nohup java -Xms2G -Xmx2G -XX:MaxMetaspaceSize=512M -XX:NativeMemoryTracking=detail ThreadOOM &

用native memory tracking查看内存占用

jcmd 37898 VM.native_memory scale=MB
37898:

Native Memory Tracking:

Total: reserved=9366MB, committed=8211MB
-                 Java Heap (reserved=2048MB, committed=2048MB)
                            (mmap: reserved=2048MB, committed=2048MB)

-                     Class (reserved=1039MB, committed=12MB)
                            (classes #433)
                            (malloc=7MB #3218)
                            (mmap: reserved=1032MB, committed=5MB)

-                    Thread (reserved=6046MB, committed=6046MB)
                            (thread #3017)
                            (stack: reserved=6032MB, committed=6032MB)
                            (malloc=10MB #18096)
                            (arena=3MB #6029)

-                      Code (reserved=130MB, committed=3MB)
                            (mmap: reserved=130MB, committed=2MB)

-                        GC (reserved=83MB, committed=83MB)
                            (malloc=8MB #123)
                            (mmap: reserved=75MB, committed=75MB)

-                  Internal (reserved=17MB, committed=17MB)
                            (malloc=17MB #34406)

-                    Symbol (reserved=1MB, committed=1MB)
                            (malloc=1MB #110)

-    Native Memory Tracking (reserved=1MB, committed=1MB)
                            (tracking overhead=1MB)
                            

显示线程占用了6G左右,jvm总共committed了8G左右。
使用top查看,常驻物理内存(RES)才占用了139M,这个和nmt显示的差距太大了吧!commited内存不就应该是RES的大小吗?
在这里插入图片描述

二 jdk8申请内存的源码分析

我看的jdk的源码:https://github.com/openjdk/jdk
分支: jdk8-b120
文件位置: hotspot/src/os/linux/vm/os_linux.cpp

reserve内存

char* os::reserve_memory(size_t bytes, char* requested_addr,
                         size_t alignment_hint) {
  return anon_mmap(requested_addr, bytes, (requested_addr != NULL));
}

static char* anon_mmap(char* requested_addr, size_t bytes, bool fixed) {
  char * addr;
  int flags;

  flags = MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS;
  if (fixed) {
    assert((uintptr_t)requested_addr % os::Linux::page_size() == 0, "unaligned address");
    flags |= MAP_FIXED;
  }

  // Map uncommitted pages PROT_READ and PROT_WRITE, change access
  // to PROT_EXEC if executable when we commit the page.
  addr = (char*)::mmap(requested_addr, bytes, PROT_READ|PROT_WRITE,
                       flags, -1, 0);

  if (addr != MAP_FAILED) {
    if ((address)addr + bytes > _highest_vm_reserved_address) {
      _highest_vm_reserved_address = (address)addr + bytes;
    }
  }

  return addr == MAP_FAILED ? NULL : addr;
}

commit内存

// NOTE: Linux kernel does not really reserve the pages for us.
//       All it does is to check if there are enough free pages
//       left at the time of mmap(). This could be a potential
//       problem.
bool os::commit_memory(char* addr, size_t size, bool exec) {
  int prot = exec ? PROT_READ|PROT_WRITE|PROT_EXEC : PROT_READ|PROT_WRITE;
  uintptr_t res = (uintptr_t) ::mmap(addr, size, prot,
                                   MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0);
  return res != (uintptr_t) MAP_FAILED;
}

不管是reserve还是commit内存,背后都是调用mmap函数

三 mmap函数分析

函数原型

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

mmap主要做文件映射,也可以用来为进程申请内存。jdk显然是用来申请内存空间。但是这个系统函数调用后,os并不会立刻分配物理内存,而是等对申请到的内存块进行具体的读写之后再进行物理内存page实际分配。

测试代码

#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    int test = 0;
    size_t initial_size = 1024*1024*50;  // 初始大小为 50MB
    size_t expanded_size = 1024*1024*512; // 扩展大小为 512MB

    // 创建映射区域
    void *ptr = mmap(NULL, initial_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    printf("Initial size: %zuKB\n", initial_size / 1024);
    scanf("%d", &test);

    // 使用 mremap 扩展映射区域的大小
    void *new_ptr = mremap(ptr, initial_size, expanded_size, MREMAP_MAYMOVE);
    if (new_ptr == MAP_FAILED) {
        perror("mremap");
        exit(EXIT_FAILURE);
    }

    printf("Expanded size: %zuKB\n", expanded_size / 1024);

    scanf("%d", &test);
    
    // 使用新的映射区域进行读写操作...

    //使用10M
    memset(new_ptr, 0, 1024 * 1024 * 10);
    scanf("%d", &test);
    
    // 使用100M
    memset(new_ptr, 0, 1024 * 1024 * 100);
    scanf("%d", &test);

    // 解除映射
    if (munmap(new_ptr, expanded_size) == -1) {
        perror("munmap");
        exit(EXIT_FAILURE);
    }

    return 0;
}

3.1 执行mmap函数

mmap函数执行后,查看top输出,虚拟内存52M接近申请的50M,而RES仅有1M
在这里插入图片描述

3.2 执行mremap

mremap执行后,查看top输出,虚拟内存涨到了514MB,接近扩容申请的512MB,RES常驻内存不变
在这里插入图片描述

3.3 执行第一个memset

接着执行第一个memset,进行内存写入。这次发现虚拟内存不变,而RES物理内存增长了10MB,这和memset的内存大小一致
在这里插入图片描述

3.4 执行第2个memset

接着执行第二个memset,写入100MB(指针位置没有变化)。虚存没有变化,RES增加了90MB。
在这里插入图片描述
使用pmp命令分析

lvsheng@lvsheng:/proc/36287$ pmap -x  41422
41422:   ./mmap
Address           Kbytes     RSS   Dirty Mode  Mapping
0000c8e2d33a0000       4       4       0 r-x-- mmap
0000c8e2d33bf000       4       4       4 r---- mmap
0000c8e2d33c0000       4       4       4 rw--- mmap
0000c8e309014000     132       4       4 rw---   [ anon ]
0000e16bc8c00000  524288  102400  102400 rw---   [ anon ]
0000e16bebe20000    1640    1088       0 r-x-- libc.so.6
0000e16bebfba000      76       0       0 ----- libc.so.6
0000e16bebfcd000      12      12      12 r---- libc.so.6
0000e16bebfd0000       8       8       8 rw--- libc.so.6
0000e16bebfd2000      48      16      16 rw---   [ anon ]
0000e16bebfdf000     156     156       0 r-x-- ld-linux-aarch64.so.1
0000e16bec018000       8       8       8 rw---   [ anon ]
0000e16bec01a000       8       0       0 r----   [ anon ]
0000e16bec01c000       4       4       0 r-x--   [ anon ]
0000e16bec01d000       8       8       8 r---- ld-linux-aarch64.so.1
0000e16bec01f000       8       8       8 rw--- ld-linux-aarch64.so.1
0000fffff236c000     132      12      12 rw---   [ stack ]
---------------- ------- ------- -------
total kB          526540  103736  102484

512MB的虚拟内存,OS分配了100MB物理内存

四 总结

在这里插入图片描述

  1. jdk通过mmap申请内存后,操作系统分配的虚拟内存,并没有分配实际的物理内存。
  2. 当Java应用程序实际写入时,OS才会分配物理内存。

所以nmt和top的RES指标的差异会很明显

参考文章


  1. https://blog.csdn.net/qq_41687938/article/details/119901916

http://www.kler.cn/a/527280.html

相关文章:

  • 验证二叉搜索数(98)
  • 【算法】动态规划专题① ——线性DP python
  • 理解动手学深度学习的自编包d2l
  • 青少年编程与数学 02-008 Pyhon语言编程基础 05课题、数据类型
  • 【Elasticsearch】match_bool_prefix 查询 vs match_phrase_prefix 查询
  • DeepSeek的使用技巧介绍
  • SARIMA介绍
  • 【TCP协议】流量控制 滑动窗口
  • 高速PCB设计指南5——电磁干扰和电磁兼容
  • CSDN的历史
  • Flink Connector 写入 Iceberg 流程源码解析_confluent icebergsinkconnector
  • git push到远程仓库时无法推送大文件
  • DeepSeek - R1:AI 推理模型的技术深度剖析与行业影响
  • 浅析 SSH 免密登录原理
  • 五. Redis 配置内容(详细配置说明)
  • C语言:创建带头结点的动态链表:解析与实现
  • Three.js 中实现自定义光圈 Shader 效果
  • 强化学习笔记——4策略迭代、值迭代、TD算法
  • 使用PaddlePaddle实现逻辑回归:从训练到模型保存与加载
  • 16进制(十六进制)和二进制之间的转换