0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

glibc導(dǎo)致的堆外內(nèi)存泄露的排查過程

OSC開源社區(qū) ? 來源:阿里云開發(fā)者 ? 2023-09-01 09:43 ? 次閱讀

阿里妹導(dǎo)讀

本文記錄一次glibc導(dǎo)致的堆外內(nèi)存泄露的排查過程。

問題現(xiàn)象

團(tuán)隊(duì)核心應(yīng)用每次發(fā)布完之后,內(nèi)存會(huì)逐步占用,不重啟或者重新部署就會(huì)導(dǎo)致整體內(nèi)存占用率超過90%。

dcb20a6c-47ef-11ee-97a6-92fbcf53809c.png

發(fā)布2天后的內(nèi)存占用趨勢(shì)

探索原因一

堆內(nèi)找到原因

出現(xiàn)這種問題,第一想到的就是集群中隨意找一臺(tái)機(jī)器,信手dump一下內(nèi)存,看看是否有堆內(nèi)存使用率過高的情況。 dcd53b36-47ef-11ee-97a6-92fbcf53809c.png 內(nèi)存泄露 dd002152-47ef-11ee-97a6-92fbcf53809c.png 泄露對(duì)象占比 發(fā)現(xiàn) 占比18.8%

問題解決

是common-division這個(gè)包引入的

dd21defa-47ef-11ee-97a6-92fbcf53809c.png 暫時(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ì)比。 dd482650-47ef-11ee-97a6-92fbcf53809c.png

發(fā)布后短時(shí)間內(nèi)有個(gè)內(nèi)存增長實(shí)屬正常,后續(xù)在做觀察。

發(fā)布第二天,順手又dump一下同一臺(tái)機(jī)器的內(nèi)存 ddd00f98-47ef-11ee-97a6-92fbcf53809c.png 由原來的18.8%4.07%的占比,降低了14%,牛皮?。?傻了眼,內(nèi)存又飆升到86%~~ 該死的迷之自信??! ddf63baa-47ef-11ee-97a6-92fbcf53809c.png 發(fā)布后內(nèi)存使用率

探索原因二

沒辦法匯報(bào)了~~~ 但是問題還是要去看看為什么會(huì)占用這么大的內(nèi)存空間的~

查看進(jìn)程內(nèi)存使用

de43352c-47ef-11ee-97a6-92fbcf53809c.png

java 進(jìn)程內(nèi)存使用率 84.9%,RES 6.8G。

查看堆內(nèi)使用情況

當(dāng)期機(jī)器配置為 4Core 8G,堆最大5G,堆使用為不足3G左右。

deb48d9e-47ef-11ee-97a6-92fbcf53809c.png 使用arthas的dashboard/memory 命令查看當(dāng)前內(nèi)存使用情況: def96b12-47ef-11ee-97a6-92fbcf53809c.png 當(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
df3a66a8-47ef-11ee-97a6-92fbcf53809c.png 如圖有很多內(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
df8c4630-47ef-11ee-97a6-92fbcf53809c.png

劇透: 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)存。

e0196218-47ef-11ee-97a6-92fbcf53809c.png

查看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
e02705d0-47ef-11ee-97a6-92fbcf53809c.png

glibc為什么會(huì)有泄露

e05ba592-47ef-11ee-97a6-92fbcf53809c.png 我們當(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)存塊 作切割和合并。

e069d978-47ef-11ee-97a6-92fbcf53809c.png

為什么是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)『: Invalid conf pair: prof:true』類似的關(guā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
然后就可以在瀏覽器中瀏覽了 e0838364-47ef-11ee-97a6-92fbcf53809c.png

與參閱文檔中結(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

e120ec44-47ef-11ee-97a6-92fbcf53809c.pnge12c03e0-47ef-11ee-97a6-92fbcf53809c.png 如上源碼可以看出,如果使用InflaterInputStream(InputStream?in)?來構(gòu)造對(duì)象usesDefaultInflater=true, 否則全部為false; 在流關(guān)閉的時(shí)候。 e1df5bac-47ef-11ee-97a6-92fbcf53809c.pnge263af92-47ef-11ee-97a6-92fbcf53809c.png

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

二方包掃描

e298f0c6-47ef-11ee-97a6-92fbcf53809c.png 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ā)布部署&觀察

e2b4e18c-47ef-11ee-97a6-92fbcf53809c.pnge2dcdd0e-47ef-11ee-97a6-92fbcf53809c.pnge3003aba-47ef-11ee-97a6-92fbcf53809c.png 這此真的舒服了~ ?

總結(jié)

探究了glibc的工作原理之后,發(fā)現(xiàn)相比jemalloc的內(nèi)存使用上確實(shí)存在高碎片率的問題,但是本次問題的根本還是在應(yīng)用層面沒有正確的關(guān)閉流加劇的堆外內(nèi)存的泄露。

總結(jié)的過程,也是學(xué)習(xí)的過程,上述分析過程歡迎評(píng)論探討。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(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)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    波特率漂移導(dǎo)致通信異常的故障排查過程

    示波器的協(xié)議解碼功能大家都不生疏,你是否有過波形看起來正常,協(xié)議參數(shù)、解碼設(shè)置都正確,卻無法正常解碼的經(jīng)歷呢?本文以UART協(xié)議為例,分享由于波特率漂移導(dǎo)致通信異常的故障排查過程。
    的頭像 發(fā)表于 01-08 13:51 ?6349次閱讀
    波特率漂移<b class='flag-5'>導(dǎo)致</b>通信異常的故障<b class='flag-5'>排查過程</b>

    Linux上對(duì)進(jìn)程進(jìn)行內(nèi)存分析和內(nèi)存泄漏定位

    內(nèi)存申請(qǐng),內(nèi)存管理器使用brk系統(tǒng)調(diào)用擴(kuò)展頂指針。M_MMAP_MAX是該進(jìn)程中最多使用mmap分配地址段的數(shù)量。如果在實(shí)際的調(diào)試過程中,懷疑某處發(fā)生了
    發(fā)表于 07-09 08:15

    分享一種內(nèi)存泄漏定位排查技巧

    常見的泄漏方式在嵌入式開發(fā)中,經(jīng)常會(huì)使用malloc,free分配釋放內(nèi)存,稍不小心就可能導(dǎo)致內(nèi)存一點(diǎn)點(diǎn)地泄露,直至
    發(fā)表于 12-17 08:13

    如何有效地排查內(nèi)存泄露的疑難問題

    不太輕易去改動(dòng)里面的代碼,這無疑也增大了排查難度。在這樣的背景下,要完成從“復(fù)雜度”如此高的代碼里面找出可能出現(xiàn)【內(nèi)存泄露】的幾行問題代碼,需要有點(diǎn)手段才行。3 解決思路根據(jù)多年對(duì)【
    發(fā)表于 09-01 14:47

    單片機(jī)C語言幾種內(nèi)存泄露總結(jié)

    程序的設(shè)計(jì)的錯(cuò)誤導(dǎo)致這部分內(nèi)存沒有被釋放,那么此后這塊內(nèi)存將不會(huì)被使用,就會(huì)產(chǎn)生Heap Leak. 這是最常見的內(nèi)存泄露。
    發(fā)表于 11-14 10:09 ?2549次閱讀
    單片機(jī)C語言幾種<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>總結(jié)

    Java開發(fā)者必須了解的內(nèi)存技術(shù)

    先來看一個(gè) Demo:在 Demo 中分配內(nèi)存用的是 allocateDirect 方法,但其內(nèi)部調(diào)用的是 DirectByteBuffer,換言之,DirectByteBuffer 才是實(shí)際操作
    發(fā)表于 07-01 10:19 ?3766次閱讀
    Java開發(fā)者必須了解的<b class='flag-5'>堆</b><b class='flag-5'>外</b><b class='flag-5'>內(nèi)存</b>技術(shù)

    DC-DC電源故障排查過程和總結(jié),珍貴的經(jīng)驗(yàn)!資料下載

    電子發(fā)燒友網(wǎng)為你提供DC-DC電源故障排查過程和總結(jié),珍貴的經(jīng)驗(yàn)!資料下載的電子資料下載,更有其他相關(guān)的電路圖、源代碼、課件教程、中文資料、英文資料、參考設(shè)計(jì)、用戶指南、解決方案等資料,希望可以幫助到廣大的電子工程師們。
    發(fā)表于 04-25 08:54 ?75次下載
    DC-DC電源故障<b class='flag-5'>排查過程</b>和總結(jié),珍貴的經(jīng)驗(yàn)!資料下載

    glibc內(nèi)存管理存在的共性問題及解決方法

    引言 對(duì)于嵌入式設(shè)備來說,用戶態(tài)內(nèi)存管理是一項(xiàng)基礎(chǔ)功能,目前主流的用戶態(tài)內(nèi)存管理庫有glibc、uclibc、tcmalloc、jemalloc等。 本文基于glibc2.17版本進(jìn)行
    的頭像 發(fā)表于 06-18 14:50 ?3125次閱讀

    什么是內(nèi)存內(nèi)存是如何分配的?

    在一般的編譯系統(tǒng)中,內(nèi)存的分配方向和棧內(nèi)存是相反的。當(dāng)棧內(nèi)存從高地址向低地址增長的時(shí)候,內(nèi)存
    的頭像 發(fā)表于 07-05 17:58 ?9895次閱讀

    Glibc內(nèi)存管理之Ptmalloc2源代碼分析

    Glibc內(nèi)存管理之Ptmalloc2源代碼分析
    發(fā)表于 07-29 09:20 ?24次下載

    Java內(nèi)部類持有外部類導(dǎo)致內(nèi)存泄露的原因以及其解決方案

    簡介 為什么要持有外部類 實(shí)例:持有外部類 實(shí)例:不持有外部類 實(shí)例:內(nèi)存泄露 不會(huì)內(nèi)存泄露的方案 簡介 「說明」 本文介紹 Java 內(nèi)部類持有外部類
    的頭像 發(fā)表于 10-08 16:32 ?946次閱讀

    mtrace分析內(nèi)存泄露

    一、mtrace分析內(nèi)存泄露 mtrace(memory trace),是 GNU Glibc 自帶的內(nèi)存問題檢測(cè)工具,它可以用來協(xié)助定位內(nèi)存
    的頭像 發(fā)表于 11-13 10:55 ?1220次閱讀
    mtrace分析<b class='flag-5'>內(nèi)存</b><b class='flag-5'>泄露</b>

    內(nèi)存是如何泄露

    作為 C++ 程序員,內(nèi)存泄露始終是懸在頭上的一顆炸彈。在過去幾年的 C++ 開發(fā)過程中,由于我們采用了一些技術(shù),我們的程序發(fā)生內(nèi)存泄露的情
    的頭像 發(fā)表于 11-13 14:13 ?394次閱讀
    <b class='flag-5'>內(nèi)存</b>是如何<b class='flag-5'>泄露</b>的

    java內(nèi)存溢出排查方法

    Java內(nèi)存溢出(Memory overflow)是指Java虛擬機(jī)(JVM)中的內(nèi)存無法滿足對(duì)象分配的需求,導(dǎo)致程序拋出OutOfMemoryError異常。
    的頭像 發(fā)表于 11-23 14:46 ?3035次閱讀

    Java怎么排查oom異常

    據(jù)量的應(yīng)用中。要排查OOM異常,需要經(jīng)過以下幾個(gè)步驟: 理解OOM異常的原因:OOM異常通常有以下幾個(gè)原因:內(nèi)存泄露、內(nèi)存溢出、內(nèi)存不足以容
    的頭像 發(fā)表于 12-05 13:47 ?1189次閱讀