• Java 进程占用 VIRT 虚拟内存超高的问题研究


    1. 现象

    最近发现线上机器 java 8 进程的 VIRT 虚拟内存使用达到了 50G+,如下图所示:

    image

    2. 不管用的 -Xmx

    首先第一想到的当然使用 java 的 -Xmx 去限制堆的使用。但是无论怎样设置,都没有什么效果。没办法,只好开始苦逼的研究。

    3. 什么是 VIRT

    现代操作系统里面分配虚拟地址空间操作不同于分配物理内存。在64位操作系统上,可用的最大虚拟地址空间有16EB,即大概180亿GB。那么在一台只有16G的物理内存的机器上,我也能要求获得4TB的地址空间以备将来使用。例如:

        void *mem = mmap(0, 4ul * 1024ul * 1024ul * 1024ul * 1024ul,
                         PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE,
                         -1, 0);
    当使用 mmap 并设置 MAP_NORESERVE 标志时,并不会要求实际的物理内存和swap空间存在。所以上述代码可以在top中看到使用了 4096g 的 VIRT 虚拟内存,这当然是不可能的,它只是表示使用了 4096GB 的地址空间而已。

    4. 为什么会用这么多地址空间

    那 Java 程序为什么会使用这么多的地址空间呢?使用“pmap -x”来查看一下:

    00007ff638021000   65404       0       0 -----    [ anon ]
    00007ff63c000000     132      36      36 rw---    [ anon ]
    00007ff63c021000   65404       0       0 -----    [ anon ]
    00007ff640000000     132      28      28 rw---    [ anon ]
    00007ff640021000   65404       0       0 -----    [ anon ]
    00007ff644000000     132       8       8 rw---    [ anon ]
    00007ff644021000   65404       0       0 -----    [ anon ]
    00007ff648000000     184     184     184 rw---    [ anon ]
    00007ff64802e000   65352       0       0 -----    [ anon ]
    00007ff64c000000     132     100     100 rw---    [ anon ]
    00007ff64c021000   65404       0       0 -----    [ anon ]
    00007ff650000000     132      56      56 rw---    [ anon ]
    00007ff650021000   65404       0       0 -----    [ anon ]
    00007ff654000000     132      16      16 rw---    [ anon ]
    00007ff654021000   65404       0       0 -----    [ anon ]
    发现有很多奇怪的64MB的内存映射,查资料发现这是 glibc 在版本 2.10 引入的 arena 新功能导致。CentOS 6/7 的 glibc 大都是 2.12/ 2.17 了,所以都会有这个问题。这个功能对每个线程都分配一个分配一个本地arena来加速多线程的执行。
    在 glibc 的 arena.c 中使用的 mmap() 调用就和之前的示例代码类似:
        p2 = (char *)mmap(aligned_heap_area, HEAP_MAX_SIZE, PROT_NONE,
                              MAP_NORESERVE | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)
    之后,只有很小的一部分地址被映射到了物理内存中:
        mprotect(p2, size, PROT_READ | PROT_WRITE)
    因此在一个多线程程序中,会有相当多的 64MB 的 arena 被分配。这个可以用环境变量 MALLOC_ARENA_MAX 来控制。在64位系统中的默认值为 128。

    5. Java 的特殊性

    Java 程序由于自己维护堆的使用,导致调用 glibc 去管理内存的次数较少。更糟的是 Java 8 开始使用 metaspace 原空间取代永久代,而元空间是存放在操作系统本地内存中,那线程一多,每个线程都要使用一点元空间,每个线程都分配一个 arena,每个都64MB,就会导致巨大的虚拟地址被分配。

    6. 结束语

    总结一下:

    • VIRT高是因为分配了太多地址空间导致。
    • 一般来说不用太在意VIRT太高,因为你有16EB的空间可以使用。
    • 如果你实在需要控制VIRT的使用,设置环境变量MALLOC_ARENA_MAX,例如hadoop推荐值为4,因为YARN使用VIRT值监控资源使用。
  • 相关阅读:
    boost::asio在VS2008下的编译错误
    Java集合框架——接口
    ACM POJ 3981 字符串替换(简单题)
    ACM HDU 1042 N!(高精度计算阶乘)
    OneTwoThree (Uva)
    ACM POJ 3979 分数加减法(水题)
    ACM HDU 4004 The Frog's Games(2011ACM大连赛区第四题)
    Hexadecimal View (2011ACM亚洲大连赛区现场赛D题)
    ACM HDU 4002 Find the maximum(2011年大连赛区网络赛第二题)
    ACM HDU 4001 To Miss Our Children Time (2011ACM大连赛区网络赛)
  • 原文地址:https://www.cnblogs.com/seasonsluo/p/java_virt.html
Copyright © 2020-2023  润新知