1. 程式人生 > >Linux上java程序虛擬記憶體VIRT佔用高的問題

Linux上java程序虛擬記憶體VIRT佔用高的問題

1. 現象

最近發現線上機器 java 8 程序的 VIRT 虛擬記憶體使用達到了 50G+,如下圖所示:

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值監控資源使用。