當Java虛擬機遇上Linux Arena內存池
故障案例一
係統環境:
RHEL 6.8 64-bit(glibc 2.12)、Sun JDK 6u45 64-bit、WLS 10.3.6
故障現象:
這裏引用一下客戶當時發郵件時提出的問題描述吧。
下麵pid 6287 weblogic進程占用7.6G的物理內存,之前隻占用5G內存。我發現隻有係統有空餘的內存,就會被java給吃掉,為什麼內存占用越來越多?
通過jmap -histo:live 6287 查看內存隻占用800多MB。
Total 12415620 891690640
此時,操作係統內存幾乎耗盡,而且用了很多Swap交換分區內存,係統性能並不是很好。
故障分析:
剛開始看到這個問題時,首先考慮可能是Native Memory Leak或JDK的Bug,然後看了下那些WebLogic進程的命令行參數:
/data/jdk1.6.0_45/bin/java -server -Xms2560m -Xmx2560m .....
從JDK入手
一看,已經是6u45了,Sun Java SE Public版的最終版本了,找來找去也沒找到匹配的Bug(當時還真找到一個看著很像的,JDK-2172773 : JVM sometimes would suddenly consume significant amount of memory,但人家是在6u14b01、5u16rev這兩個版本開始,都已經修複了),看來不能從JDK Bug這個方向入手分析了。
從Native Memory Leak入手
但是這個JDK版本也比較尷尬,沒有提供Native Memory Trace的功能參數或命令支持(from 7u40版本開始提供),要知道Sun Java SE內部的內存區域很複雜,常見或不常見的很多區域,下麵拿JDK 8版本(6版本大同小異)的內存區域為例展示一下:
沒有直接的診斷工具的情況下,隻能通過一些操作係統命令對這些RES、VIRT內存占用都高的JVM進程的內存使用輸出結果做比較,以從中找出一些蛛絲馬跡。最終,確定使用pmap這個命令(程序),結果看到如下的輸出結果:
這裏發現一個規律,65484 + 52 = 65536 KB, 65420 + 116 = 65536 KB, 65036 + 500 = 65536 KB .... ,進程內有大量的這種64MB大小的連續內存塊。
然後,就是需要知道這是什麼東東,Google一把,得知anon是Anonymous memory段的縮寫。
Anonymous memory is a memory mapping with no file or device backing it.
This is how programs allocate memory from the operating system for use
by things like the stack and heap.
Anonymous memory的使用會使虛擬內存(VIRT)、物理內存(RSS)使用率上升。
而且,找到兩篇講的很清晰的文檔了:
那這個問題就是Arena內存池數太多,且分配使用的內存較多,不斷上漲,導致的WebLogic/Java虛擬機進程RES、VIRT內存使用超高。
解決辦法:
直接想到的解決思路就是限製Arena內存池的個數。考慮到Arena內存池的主要是用來提高glibc內存分配性能的,而且根據Hadoop、Redis等產品的最佳實踐建議,嚐試設置MALLOC_ARENA_MAX環境變量值為4:
export MALLOC_ARENA_MAX=4
設置完重啟WebLogic,然而意外的是,設置完以後Java虛擬機/WebLogic進程RES、VIRT內存使用依然很高:
後來我查到glibc 2.12版本有幾個Arena內存管理的Bug,可能導致參數設置不生效或生效後內存繼續往上漲:
Bug 799327 - MALLOC_ARENA_MAX=1 does not work in RHEL 6.2(glibc 2.12)
Bug 20425 - unbalanced and poor utilization of memory in glibc arenas may cause memory bloat and subsequent OOM
Bug 11261 - malloc uses excessive memory for multi-threaded applications
然後,我們考慮到將MALLOC_ARENA_MAX設置為4已經影響了一些Arena內存池管理上的一些性能,要繼續使用MALLOC_ARENA_MAX參數,就需要升級glibc的版本,升級完還不確定高版本的glibc與其他包兼容性上有什麼影響,畢竟是操作係統底層的包了,所以就直接使用了Google的tcmalloc替代操作係統自帶的glibc管理內存。有資料顯示,使用tcmalloc以後,Web Server的吞吐量得以提升(先嚐試的jemalloc,但是啟動後會影響操作係統命令的執行,所以,就用了tcmalloc):
替換為tcmalloc以後,WebLogic/Java虛擬機進程使用的RES、VIRT內存明顯下降到合理值,問題得以解決。
故障案例二
係統環境:
RHEL 6.5 64-bit(glibc 2.12)、Sun JDK 5u22 32-bit、WLS 10.0.2
故障現象:
客戶核心係統由於業務的需要,新加了一個節點,沿用原先的相同的操作係統、WebLogic、JDK版本,並且保證所有WebLogic參數配置都是相同的情況下,經常出現Java虛擬機Crash的情況:
file hs_err_pid28384.log :
#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
# SIGSEGV (0xb) at pc=0xf6f8405d, pid=28384, tid=815790960
#
# Java VM: Java HotSpot(TM) Server VM (1.5.0_22-b03 mixed mode)
# Problematic frame:
# V [libjvm.so+0x24405d]
#
......
故障分析:
由於這是32-bit的JDK,那就是Native Memory使用過高,超過了尋址空間的限製(4G,默認User Space : Kernel Space = 3 : 1,但在目前的Linux內核版本中,大多數32-bit的進程運行在64-bit操作係統上,幾乎都可以用到所有的4G用戶空間)。
在做分析之前,為擴大Native Memory空間,我降低了Java Heap Size(-Xms、-Xmx)和 Perm Size(-XX:PermSize、-XX:MaxPermSize)的值,以此來放緩Native Memory上漲的形勢(客戶不同意使用64-bit JDK)。
接下來,就是分析什麼東東造成Native Memory使用持續上漲了。這裏,還是用了pmap去看下Native Memory的使用和變化:
這裏也看到了許多984 + 40 = 1024 KB, 1012 + 12 = 1024 KB, 988 + 36 = 1024 KB .... ,進程內有大量的這種1MB大小的連續內存塊,而且,通過多次不同時間點的pmap -px輸出結果來看,這種1MB大小的內存塊還在不斷增長。到這裏,聯想到上麵的連續的64MB大小的內存快,迅速找到了當時留的文檔鏈接
這篇文章裏明確提到:
These memory pools are called arenas and the implementation is in arena.c. The first important macro is HEAP_MAX_SIZE which is the maximum size of an arena and it is basically 1MB on 32-bit and 64MB on 64-bit:
HEAP_MAX_SIZE = (2 * DEFAULT_MMAP_THRESHOLD_MAX)
32-bit [DEFAULT_MMAP_THRESHOLD_MAX = (512 * 1024)] = 1,048,576 (1MB)
64-bit [DEFAULT_MMAP_THRESHOLD_MAX = (4 * 1024 * 1024 * sizeof(long))] = 67,108,864 (64MB)
32-bit應用程序Arena的大小最大為1MB,64-bit應用程序最大為64MB,這次終於見識到了。
32-bit應用程序,sizeof(long) = 4 bit,那麼這個計算係數就是 2(sizeof(long) == 4 ? 2 : 8)
按照Arena數量最大值的計算公式:
**maximum number of arenas = NUMBER_OF_CPU_CORES * (sizeof(long) == 4 ? 2 : 8) **計算,當前係統80核CPU,那麼理論上該Java虛擬機進程最大的Arena值就是 80 * 2 * 1(MB)= 160MB,但實際上,通過pmap觀察到這個進程這種1MB大小的匿名內存塊都有700多MB,又看了下當前操作係統中glibc的版本是1.12,聯想到故障案例一中設置的MALLOC_ARENA_MAX=4在1.12版本都不生效的問題,遇到這種現象就不足為奇了。
目前,RHEL 5.x、6.x、7.3中使用的glibc版本都比較舊(都是2012年及之前的版本了,7.3中使用的glibc版本是2.17,6.x中使用的glibc版本是2.12),可考慮在不是很重要的係統中保持glibc版本始終為最新,然後再觀察Arena內存的使用。
解決辦法:
這次直接設置MALLOC_ARENA_MAX=1,隻保留main arena,禁用掉per thread arena內存池,使其與RHEL 5.x版本保持一致,問題得以解決,設置完,Java虛擬機不再Crash,pmap監控WebLogic/JVM進程使用的內存增長明顯變少、變緩。當然,設置完MALLOC_ARENA_MAX=1,該WebLogic/JVM進程的Native Memory分配、重用、回收等性能多多少少會受到一些影響,也可以使用Google的tcmalloc解決。
總結
通過這兩個故障案例可以看出,從glibc 2.11(為應用係統在多核心CPU和多Sockets環境中高伸縮性提供了一個動態內存分配的特性增強)版本開始引入了per thread arena內存池,Native Heap區被打散為sub-pools ,這部分內存池叫做Arena內存池。也就是說,以前隻有一個main arena,目前是一個main arena(還是位於Native Heap區) + 多個per thread arena,多個線程之間不再共用一個arena內存區域了,保證每個線程都有一個堆,這樣避免內存分配時需要額外的鎖來降低性能。main arena主要通過brk/sbrk係統調用去管理,per thread arena主要通過mmap係統調用去分配和管理。
我們來看下線程申請per thread arena內存池的流程:
Unlimited MALLOC_ARENAS_MAX
- Thread asks for an per thread arena
- Thread gets an per thread arena
- Thread fills arena, never frees memory
- Thread asks for an new per thread arena .- ...........
- When no more per thread arena will be created, reused_arena function will be called to reuse arena already existed.
我們知道了main arena、per thread arena,那麼一個Java虛擬機進程究竟能創建多少個arena、每個arena的大小又是多少那?這部分理論知識比較常見,還不清楚的童鞋,我再囉嗦一下,貼一遍:
一個32位的應用程序進程,最大可創建 2 * CPU總核數個arena內存池(MALLOC_ARENA_MAX),每個arena內存池大小為1MB
一個64位的應用程序進程,最大可創建 8 * CPU總核數個arena內存池(MALLOC_ARENA_MAX),每個arena內存池大小為64MB
理論歸理論,glibc 2.12版本(也就是RHEL 6.x中默認自帶的)在arena內存分配和管理上,由於不少的Bug或目前我還沒完全弄明白的理論的存在,實際上用pmap看到的1MB或64MB的anonymous memory(縮寫為anon)並不完全遵循MALLOC_ARENA_MAX個數設置。
除故障案例一中提到的幾處bug文章外,還有兩個地方的文檔顯示,MALLOC_ARENA_MAX個數並不是按照設計那樣的工作,多線程應用經常遇到RSS、VIRT內存持續升高的情況,尤其是CPU核數多的係統環境。
glibc incorrectly allocated too much memory due to a race condition
within its own malloc routines. This could cause a multi-threaded
application to allocate more memory than was expected.
如果不考慮內存分配的性能,遇到這樣的問題時,可使用export MALLOC_ARENA_MAX=1禁用per thread arena,隻用main arena,多個線程共用一個arena內存池。如果考慮到性能,可使用tcmalloc或jemalloc替代操作係統自帶的glibc管理內存。
上麵兩個故障案例都是Sun HotSpot JVM的,另外,IBM JDK和Oracle JRockit在RHEL 6.x操作係統環境運行時,也會遇到Arena內存使用上的問題,詳見:
原文發布時間為:2017-10-27
本文作者:劉韜
本文來自雲棲社區合作夥伴“數據和雲”,了解相關信息可以關注“數據和雲”微信公眾號shuju
最後更新:2017-10-27 13:03:59