JVM占用VIRT虚拟内存高问题研究

1. 现象

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

2. 无论用的 -Xmx

首先第一想到的固然使用 java 的 -Xmx 去限制堆的使用。可是不管怎样设置,都没有什么效果。没办法,只好开始苦逼的研究。多线程

3. 什么是 VIRT

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

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 的地址空间而已。spa

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)code

以后,只有很小的一部分地址被映射到了物理内存中:
    mprotect(p2, size, PROT_READ | PROT_WRITE)
所以在一个多线程程序中,会有至关多的 64MB 的 arena 被分配。这个能够用环境变量 MALLOC_ARENA_MAX 来控制。在64位系统中的默认值为 128。进程

5. Java 的特殊性

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

6. 结束语

总结一下:hadoop

  1. VIRT高是由于分配了太多地址空间致使。
  2. 通常来讲不用太在乎VIRT过高,由于你有16EB的空间可使用。
  3. 若是你实在须要控制VIRT的使用,设置环境变量MALLOC_ARENA_MAX,例如hadoop推荐值为4,由于YARN使用VIRT值监控资源使用。
相关文章
相关标签/搜索