阿里妹導(dǎo)讀
本文記錄一次glibc導(dǎo)致的堆外內(nèi)存泄露的排查過程。
問題現(xiàn)象
團(tuán)隊(duì)核心應(yīng)用每次發(fā)布完之后,內(nèi)存會(huì)逐步占用,不重啟或者重新部署就會(huì)導(dǎo)致整體內(nèi)存占用率超過90%。
發(fā)布2天后的內(nèi)存占用趨勢(shì)
探索原因一
堆內(nèi)找到原因
出現(xiàn)這種問題,第一想到的就是集群中隨意找一臺(tái)機(jī)器,信手dump一下內(nèi)存,看看是否有堆內(nèi)存使用率過高的情況。 內(nèi)存泄露 泄露對(duì)象占比 發(fā)現(xiàn) 占比18.8%
問題解決
是common-division這個(gè)包引入的
暫時(shí)性修復(fù)方案
當(dāng)前加載俄羅斯(RU)國際地址庫,改為一個(gè)小國家地址庫 以色列(IL)
當(dāng)前業(yè)務(wù)使用場景在補(bǔ)發(fā)場景下會(huì)使用,添加打點(diǎn)日志,確保是否還有業(yè)務(wù)在使用該服務(wù),沒人在用的話,直接下掉(后發(fā)現(xiàn),確還有業(yè)務(wù)在用呢 )。
完美解決問題,要的就是速度!!發(fā)布 ~~上線??!順道記錄下同一臺(tái)機(jī)器的前后對(duì)比。
發(fā)布后短時(shí)間內(nèi)有個(gè)內(nèi)存增長實(shí)屬正常,后續(xù)在做觀察。
發(fā)布第二天,順手又dump一下同一臺(tái)機(jī)器的內(nèi)存 由原來的18.8%到4.07%的占比,降低了14%,牛皮?。?傻了眼,內(nèi)存又飆升到86%~~ 該死的迷之自信??! 發(fā)布后內(nèi)存使用率
探索原因二
沒辦法匯報(bào)了~~~ 但是問題還是要去看看為什么會(huì)占用這么大的內(nèi)存空間的~
查看進(jìn)程內(nèi)存使用
java 進(jìn)程內(nèi)存使用率 84.9%,RES 6.8G。
查看堆內(nèi)使用情況
當(dāng)期機(jī)器配置為 4Core 8G,堆最大5G,堆使用為不足3G左右。
使用arthas的dashboard/memory 命令查看當(dāng)前內(nèi)存使用情況: 當(dāng)前堆內(nèi)+非堆內(nèi)存加起來,遠(yuǎn)不足當(dāng)前RES的使用量。那么是什么地方在占用內(nèi)存??
開始初步懷疑是『堆外內(nèi)存泄露』
開啟NMT查看內(nèi)存使用
筆者是預(yù)發(fā)環(huán)境,正式環(huán)境開啟需謹(jǐn)慎,本功能有5%-10%的性能損失!??!
-XX:NativeMemoryTracking=detail jcmd pid VM.native_memory如圖有很多內(nèi)存是Unknown(因?yàn)槭穷A(yù)發(fā)開啟,相對(duì)占比仍是很高)。
概念 NMT displays “committed” memory, not "resident" (which you get through the ps command). In other words, a memory page can be committed without considering as aresident(until it directly accessed).
rssAnalyzer 內(nèi)存分析
筆者沒有使用,因?yàn)楸竟δ芘cNMT作用類似,暫時(shí)沒有截圖了~ rssAnalyzer(內(nèi)部工具),可以通過oss在預(yù)發(fā)/線上下載。
通過NMT查看內(nèi)存使用,基本確認(rèn)是堆外內(nèi)存泄露。 剩下的分析過程就是確認(rèn)是否堆外泄露,哪里在泄露。
堆外內(nèi)存分析
查了一堆文檔基本思路就是
pmap 查看內(nèi)存地址/大小分配情況
確認(rèn)當(dāng)前JVM使用的內(nèi)存管理庫是哪種
分析是什么地方在用堆外內(nèi)存。
內(nèi)存地址/大小分配情況
pmap 查看
pmap -x 2531 | sort -k 3 -n -r
劇透: 32位系統(tǒng)中的話,多為1M 64位系統(tǒng)中,多為64M。
strace 追蹤
由于系統(tǒng)對(duì)內(nèi)存的申請(qǐng)/釋放是很頻繁的過程,使用strace的時(shí)候,無法阻塞到自己想要查看的條目,推薦使用pmap。
strace -f -e"brk,mmap,munmap" -p 2853 原因: 對(duì) heap 的操作,操 作系統(tǒng)提供了 brk()函數(shù),C 運(yùn)行時(shí)庫提供了 sbrk()函數(shù);對(duì) mmap 映射區(qū)域的操作,操作系 統(tǒng)提供了 mmap()和 munmap()函數(shù)。sbrk(),brk() 或者 mmap() 都可以用來向我們的進(jìn)程添 加額外的虛擬內(nèi)存。Glibc 同樣是使用這些函數(shù)向操作系統(tǒng)申請(qǐng)?zhí)摂M內(nèi)存。
查看JVM使用內(nèi)存分配器類型
發(fā)現(xiàn)很大量為[anon](匿名地址)的64M內(nèi)存空間被申請(qǐng)。通過附錄參考的一些文檔發(fā)現(xiàn)很多都提到64M的內(nèi)存空間問題(glibc內(nèi)存分配器導(dǎo)致的),抱著試試看的態(tài)度,準(zhǔn)備看看是否為glibc。
cd /opt/taobao/java/bin ldd java
glibc為什么會(huì)有泄露
我們當(dāng)前使用的glibc的版本為2.17。說到這里可能簡單需要介紹一下glibc的發(fā)展史。
『V1.0時(shí)代』Doug Lea Malloc 在Linux實(shí)現(xiàn),但是在多線程中,存在多線程競爭同一個(gè)分配分配區(qū)(arena)的阻塞問題。
『V2.0時(shí)代』Wolfram Gloger 在 Doug Lea 的基礎(chǔ)上改進(jìn)使得 Glibc 的 malloc 可以支持多線程——ptmalloc。
glibc內(nèi)存釋放機(jī)制(可能出現(xiàn)泄露時(shí)機(jī))
調(diào)用free()時(shí)空閑內(nèi)存塊可能放入 pool 中,不一定歸還給操作系統(tǒng)。
.收縮堆的條件是當(dāng)前 free 的塊大小加上前后能合并 chunk 的大小大于 64KB、,并且 堆頂?shù)拇笮∵_(dá)到閾值,才有可能收縮堆,把堆最頂端的空閑內(nèi)存返回給操作系統(tǒng)。
『V2.0』為了支持多線程,多個(gè)線程可以從同一個(gè)分配區(qū)(arena)中分配內(nèi)存,ptmalloc 假設(shè)線程 A 釋放掉一塊內(nèi)存后,線程 B 會(huì)申請(qǐng)類似大小的內(nèi)存,但是 A 釋放的內(nèi) 存跟 B 需要的內(nèi)存不一定完全相等,可能有一個(gè)小的誤差,就需要不停地對(duì)內(nèi)存塊 作切割和合并。
為什么是64M
回到前面說的問題,為什么會(huì)創(chuàng)建這么多的64M的內(nèi)存區(qū)域。這個(gè)跟glibc的內(nèi)存分配器有關(guān)下的,間作介紹。 V2.0版本的glibc內(nèi)存分配器,將分配區(qū)域分配主分配區(qū)(main arena)和非主分配區(qū)(non main arena)(在v1.0時(shí)代,只有一個(gè)主分配區(qū),每次進(jìn)行分配的時(shí)候,需要對(duì)主分配區(qū)進(jìn)行加鎖,2.0支持了多線程,將分配區(qū)通過環(huán)形鏈表的方式進(jìn)行管理),每一個(gè)分配區(qū)利用互斥鎖使線程對(duì)于該分配區(qū)的訪問互斥。
主分配區(qū):可以通過sbrk/mmap進(jìn)行分配。
非主分配區(qū),只可以通過mmap進(jìn)行分配。
其中,mmap每次申請(qǐng)內(nèi)存的大小為HEAP_MAX_SIZE(32 位系統(tǒng)上默認(rèn)為 1MB,64 位系統(tǒng)默 認(rèn)為 64MB)。
哪里在泄露
既然知道了存在堆外內(nèi)存泄露,就要查一下到低是什么地方的內(nèi)存泄露。參考?xì)v史資料,可以使用jemalloc工具進(jìn)行排查。
配置dump內(nèi)存工具(jemalloc)
由于系統(tǒng)裝載的是glibc,所以可以自己在不升級(jí)jdk的情況下編譯一個(gè)jemalloc。
github下載比較慢,上傳到oss,再做下載。
sudo yum install -y git gcc make graphviz wget -P /home/admin/general-aftersales https://xxxx.oss-cn-zhangjiakou.aliyuncs.com/jemalloc-5.3.0.tar.bz2 && mkdir /home/admin/general-aftersales/jemalloc && cd /home/admin/general-aftersales/ && tar -jxcf jemalloc-5.3.0.tar.bz2 && cd /home/admin/xxxxx/jemalloc-5.3.0/ && ./configure --enable-prof && make && sudo make install export LD_PRELOAD=/usr/local/lib/libjemalloc.so.2 MALLOC_CONF="prof:true,lg_prof_interval:30,lg_prof_sample:17,prof_prefix:/home/admin/general-aftersales/prof_prefix
核心配置
make之后,需要啟用prof,否則會(huì)出現(xiàn)『
配置環(huán)境變量
LD_PRELOAD 掛載本次編譯的庫
MALLOC_CONF 配置dump內(nèi)存的時(shí)機(jī)。
"lg_prof_sample:N",平均每分配出 2^N 個(gè)字節(jié) 采一次樣。當(dāng) N = 0 時(shí),意味著每次分配都采樣。
"lg_prof_interval:N",分配活動(dòng)中,每流轉(zhuǎn) 1 ? 2^N 個(gè)字節(jié),將采樣統(tǒng)計(jì)數(shù)據(jù)轉(zhuǎn)儲(chǔ)到文件。
重啟應(yīng)用
./appctl restart
監(jiān)控內(nèi)存dump文件
如果上述配置成功,會(huì)在自己配置的prof_prefix 目錄中生成相應(yīng)的dump文件。 然后將文件轉(zhuǎn)換為svg格式
jeprof --svg /opt/taobao/java/bin/java prof_prefix.36090.9.i9.heap > 36090.svg然后就可以在瀏覽器中瀏覽了
與參閱文檔中結(jié)果一致,有通過Java java.util.zip.Inflater調(diào)用JNI申請(qǐng)內(nèi)存,進(jìn)而導(dǎo)致了內(nèi)存泄露。
既然找到了哪里存在內(nèi)存泄露,找到使用的地方就很簡單了。
發(fā)現(xiàn)元兇
通過arthas 的stack命令查看某個(gè)方法的調(diào)用棧。
statck java.util.zip.Inflater
java.util.zip.InflaterInputStream
如上源碼可以看出,如果使用InflaterInputStream(InputStream?in)?來構(gòu)造對(duì)象usesDefaultInflater=true, 否則全部為false; 在流關(guān)閉的時(shí)候。
end()是native方法。
只有在『usesDefaultInflater=true』的時(shí)候,才會(huì)調(diào)用free()將內(nèi)存歸嘗試歸還OS,依據(jù)上面的內(nèi)存釋放機(jī)制,可能不會(huì)歸還,進(jìn)而導(dǎo)致內(nèi)存泄露。
comp.taobao.pandora.loader.jar.ZipInflaterInputStream
二方包掃描
ZipInflaterInputStream 的流關(guān)閉使用的是父類java.util.zip.InflaterInputStream,構(gòu)造器使用public?InflaterInputStream(InputStream?in,?Inflater?inf,?int?size) 這樣如上『usesDefaultInflater=false』,在關(guān)閉流的時(shí)候,不會(huì)調(diào)用end()方法,導(dǎo)致內(nèi)存泄露。 com.taobao.pandora.loader.jar.ZipInflaterInputStream 源自pandora ,咨詢了相關(guān)負(fù)責(zé)人之后,發(fā)現(xiàn)2年前就已經(jīng)修復(fù)此內(nèi)存泄露問題了。
最低版本要求 sar包里的 pandora 版本,要大于等于 2.1.17
問題解決
升級(jí)ajdk版本
需要咨詢一下jdk團(tuán)隊(duì)的同學(xué),需要使用jemalloc作為內(nèi)存分配器的版本。
升級(jí)pandora版本
如上所說,版本高于2.1.17即可。
我們是團(tuán)隊(duì)是統(tǒng)一做的基礎(chǔ)鏡像,找相關(guān)的同學(xué)做了dockerfile from的升級(jí)。
發(fā)布部署&觀察
這此真的舒服了~ ?
總結(jié)
探究了glibc的工作原理之后,發(fā)現(xiàn)相比jemalloc的內(nèi)存使用上確實(shí)存在高碎片率的問題,但是本次問題的根本還是在應(yīng)用層面沒有正確的關(guān)閉流加劇的堆外內(nèi)存的泄露。
總結(jié)的過程,也是學(xué)習(xí)的過程,上述分析過程歡迎評(píng)論探討。
審核編輯:湯梓紅
-
內(nèi)存
+關(guān)注
關(guān)注
8文章
2966瀏覽量
73812 -
Glibc
+關(guān)注
關(guān)注
0文章
9瀏覽量
7491 -
內(nèi)存泄露
+關(guān)注
關(guān)注
0文章
6瀏覽量
1977
原文標(biāo)題:實(shí)戰(zhàn)總結(jié)|記一次glibc導(dǎo)致的堆外內(nèi)存泄露
文章出處:【微信號(hào):OSC開源社區(qū),微信公眾號(hào):OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論