【恩墨學院】當Java虛擬機器遇上Linux Arena記憶體池

恩墨學院發表於2018-03-20

【恩墨學院】當Java虛擬機器遇上Linux Arena記憶體池


劉韜,雲和恩墨中介軟體服務交付團隊專家


Java開發出身,10年WebLogic相關開發、運維工作經驗,熟悉SOA、現代業務系統架構中各層元件,尤其擅長故障處理、效能最佳化等工作。


故障案例一


系統環境:


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版本大同小異)的記憶體區域為例展示一下:


OCM 培訓課程

(圖片來源於SAP公司某技術專家在OOW演講時的一篇文章)


沒有直接的診斷工具的情況下,只能透過一些作業系統命令對這些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)使用率上升。


而且,找到兩篇講的很清晰的文件了:

JAVA 程式在64位LINUX下佔用巨大記憶體的分析

文章連結 :https://blog.chou.it/2014/03/java-consume-huge-memory-on-64bit-linux


Linux glibc >= 2.11 (RHEL 6) malloc may show excessive virtual memory usage

文章連結 :https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en


那這個問題就是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大小的記憶體快,迅速找到了當時留的文件連結


Linux glibc >= 2.10 (RHEL 6) malloc may show excessive virtual memory usage

文章連結:https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en


這篇文章裡明確提到:


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.


RHSA-2012:0058 - Security Advisory

文章地址:


Linux check M_ARENA_TEST, and M_ARENA_MAX ?

文章地址:


如果不考慮記憶體分配的效能,遇到這樣的問題時,可使用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記憶體使用上的問題,詳見:


IBM JDK虛擬記憶體使用量過高

Linux glibc >= 2.10 (RHEL 6) malloc may show excessive virtual memory usage(文章開頭部分)

連結地址:https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en


Oracle JRockit虛擬機器print_memusage輸出的other記憶體使用過高

"Other" Allocation Reported By JRCMD print_memusage Is Too High And Causing An OutOfMemory Issue (Doc ID 2073773.1) (請在My Oracle Support站點搜尋文章號)


恩墨學院隸屬於雲和恩墨(北京)資訊科技有限公司,致力於提供專業高水準的與大資料培訓服務,挖掘培養大資料與資料庫人才。恩墨學院提供包括個人實戰技能培訓、個人認證培訓、企業內訓在內的全方位大資料和資料庫技術培訓。ACE級別超強師資,配備專業實驗室,沉浸式學習與訓練,專業實驗室、配備專業助教指導訓練。能迅速融入專家圈子,業內資源豐富,迅速積累職場人脈。課程包括:班、Oracle 、Oracle OCP考試等。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28530558/viewspace-2152027/,如需轉載,請註明出處,否則將追究法律責任。

相關文章