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值監控資源使用。
相關文章
相關標籤/搜索