glibc(ptmalloc)内存暴增问题解决
from:http://blog.chinaunix.net/uid-18770639-id-3385860.html
點(diǎn)擊(此處)折疊或打開
運(yùn)行這個(gè)測(cè)試程序,發(fā)現(xiàn)Glibc內(nèi)存暴增,程序已經(jīng)把內(nèi)存返回給了Glibc庫,但Glibc庫卻沒有把內(nèi)存歸還給操作系統(tǒng)。
分析:
ptmalloc使用chunk結(jié)構(gòu)來實(shí)現(xiàn)內(nèi)存管理。用戶free掉的內(nèi)存并不是都會(huì)馬上歸還給系統(tǒng),ptmalloc會(huì)統(tǒng)一管理heap和mmap映射區(qū)域中的空閑chunk。當(dāng)用戶進(jìn)行下一次分配請(qǐng)求時(shí),ptmalloc會(huì)首先試圖在空閑的chunk中挑選一塊給用戶,這樣就避免了頻繁的系統(tǒng)調(diào)用,降低了內(nèi)存分配的開銷。對(duì)于空閑的chunk,ptmalloc采用分箱式內(nèi)存管理方式,根據(jù)空閑chunk的大小和處于的狀態(tài)將其放在三個(gè)不同的容器中。
bins:ptmalloc將相似大小的chunk用雙向鏈表鏈接起來,這樣的一個(gè)鏈表被稱為一個(gè)bin。bins有128個(gè)隊(duì)列,前64個(gè)隊(duì)列是定長(zhǎng)的(small bins),每隔8個(gè)字節(jié)大小的塊分配在一個(gè)隊(duì)列,后面的64個(gè)隊(duì)列是不定長(zhǎng)的(largebins),就是在一個(gè)范圍長(zhǎng)度的都分配在一個(gè)隊(duì)列中。所有長(zhǎng)度小于512字節(jié)的都分配在定長(zhǎng)的隊(duì)列中,后面的64個(gè)隊(duì)列是變長(zhǎng)的隊(duì)列,每個(gè)隊(duì)列中的chunk都是從大到小排列的。
?unsort隊(duì)列(只有一個(gè)隊(duì)列),它是一個(gè)cache,所有free下來的如果要進(jìn)入bins隊(duì)列中都要經(jīng)過unsort隊(duì)列,分配內(nèi)存時(shí)會(huì)查看unsorted bin中是否有合適的chunk,如果找到滿足條件的chunk,則直接返回給用戶,否則將unsorted bin中所有chunk放入bins中。
fastbins,大約有10個(gè)定長(zhǎng)隊(duì)列,它是一個(gè)高速緩沖,所有free下來的并且長(zhǎng)度是小于max_fast(默認(rèn)80B)的chunk就會(huì)進(jìn)入這種隊(duì)列中。進(jìn)入此隊(duì)列的chunk在free的時(shí)候并不修改使用位,目的是為了避免被相鄰的塊合并掉。
如果內(nèi)存塊是空閑的,它會(huì)掛在其中的一個(gè)隊(duì)列中,它是通過復(fù)用的方式,使用空閑chunk的第3個(gè)字和第4個(gè)字當(dāng)作它的前鏈和后鏈(變長(zhǎng)塊是第5個(gè)字和第6個(gè)字)。
?
malloc的步驟:
1.????????先在fastbins中找,如果能找到,從隊(duì)列中取下后(不需要再置使用位為1)立刻返回;
2.????????判斷需求的塊是否在small bins(bins的前64個(gè)bin)范圍,如果在小箱子范圍,并且剛好有滿足需求的塊,則直接返回內(nèi)存地址;
3.????????到了這一步,說明需要分配的是一塊大內(nèi)存,或者小箱子里找不到合適的chunk;這個(gè)時(shí)候,會(huì)觸發(fā)consolidate,ptmalloc首先會(huì)遍歷fastbins中的chunk,將相鄰的chunk合并,并鏈接到unsorted bin中(因?yàn)樵诖笙渥诱乙话愣家懈?#xff0c;所以要優(yōu)先合并,避免過多碎片);
4.????????在unsort bin中取出一個(gè)chunk,如果能找到剛好和想要的chunk相同大小的chunk,立刻返回,如果不是想要的chunk大小的chunk,就把它插入到bins對(duì)應(yīng)的隊(duì)列中去,轉(zhuǎn)到2。
5.????????到了這一步,說明需要分配的是一塊大的內(nèi)存,或者small bins和unsorted bin中都找不到合適的chunk,并且fastbins和unsorted bin中所有的chunk都清楚干凈了。在large bins中找,找到一個(gè)最小的能符合需求的chunk從隊(duì)列中取下,如果剩下的大小還能建一個(gè)chunk,就把chunk分成兩個(gè)部分,把剩下的chunk插入到unsort隊(duì)列中取,把chunk的內(nèi)存地址返回;
6.????????如果搜索fastbins和bins都沒有找到合適的chunk,那么就需要操作topchunk(是堆頂?shù)囊粋€(gè)chunk,不會(huì)放在任何一個(gè)隊(duì)列里)來進(jìn)行分配了。在topchunk找,如果能切出符合要求的,把剩下的一部分當(dāng)作topchunk,然后返回內(nèi)存地址;
7.????????到了這一步說明topchunk也不能滿足分配要求,就只能調(diào)用sysalloc,其實(shí)就是增長(zhǎng)堆了,然后返回內(nèi)存地址。
free的步驟:
1.????????判斷所需釋放的chunk是否為mmapedchunk,如果是,則調(diào)用munmap釋放mmaped chunk,解除內(nèi)存空間映射,該空間不再有效,然后立刻返回;
2.????????如果和topchunk相鄰,直接和topchunk合并,不會(huì)放到其他的空閑隊(duì)列中取,然后立刻返回;
3.????????如果釋放的大小小于max_fast(80字節(jié)),就把它掛到fastbins中去返回,使用位仍然為1,當(dāng)然更不會(huì)去合并相鄰塊,然后立刻返回;
4.????????如果釋放塊得大小介于80—128K,把chunk的使用位置為0,判斷前一個(gè)chunk是否處于使用中,如果前一塊也是空閑塊,則合并,并轉(zhuǎn)入下一步;
5.????????判斷當(dāng)前釋放chunk的下一個(gè)塊是否為topchunk,如果是,則轉(zhuǎn)到第7步,否則轉(zhuǎn)下一步;
6.????????判斷下一個(gè)chunk是否處在使用中,如果也是空閑的,則合并,并將合并后的chunk掛到unsort隊(duì)列中去;
7.????????如果執(zhí)行到了這一步,說明釋放了一個(gè)與top chunk相鄰的chunk;則無論它有多大,都將它與topchunk合并,并更新topchunk的大小等信息,轉(zhuǎn)下一步;
8.????????如果合并后的大小大于FASTBIN_CONSOLIDATION_THRESHOLD(64K),也會(huì)觸發(fā)consolidate,即fastbins的合并操作,合并后的chunk會(huì)被放到unsortedbin中,fastbins將變?yōu)榭?#xff0c;操作完成之后轉(zhuǎn)下一步;
9.????????試圖收縮堆。(判斷topchunk的大小是否大于mmap的收縮閾值,默認(rèn)為128KB)。
?
ptmalloc對(duì)于大于128K的塊通過mmap方式來分配,小于128K(mmap分配閾值)的塊在heap中分配。堆是通過brk的方式來增加或壓縮的,如果在現(xiàn)有的堆中不能找到合適的chunk,會(huì)通過增長(zhǎng)堆的方式來滿足分配,如果堆頂?shù)目臻e塊超過一定的閾值會(huì)收縮堆,所以只要堆頂?shù)目臻g沒釋放,堆是一直不會(huì)收縮的。因?yàn)閜tmalloc的內(nèi)存收縮是從top chunk開始,如果與top chunk(堆頂?shù)囊粋€(gè)chunk)相鄰的那個(gè)chunk在內(nèi)存池中沒有釋放,top chunk以下的空閑內(nèi)存都無法返回給系統(tǒng),即使這些空閑內(nèi)存有幾十個(gè)G也不行。
按照這個(gè)測(cè)試程序分配后,內(nèi)存變成由小塊和大塊交替出現(xiàn),釋放小塊的時(shí)候,直接把小塊放在fastbins中取,而且他的使用位還是1,釋放大塊的時(shí)候,它試圖合并相鄰的塊,但是和它相鄰的塊的使用位還是1,所以它不能把相鄰的塊合并起來,而且釋放的塊的大小小于64K,也不會(huì)觸發(fā)consolidate,即不會(huì)把fastbins清空,所以當(dāng)所有的塊都被釋放完后,所有的小塊都在fastbins里面,使用位都還是1,大塊都掛在unsort隊(duì)列里面。全部都無法合并。所以使用的堆更加無法壓縮。如果在循環(huán)后面再分配2000字節(jié)然后釋放的話,所有內(nèi)存將全部被清空,這是因?yàn)樵偕暾?qǐng)2000字節(jié)的時(shí)候,由malloc的第二步,程序會(huì)先調(diào)用consolidate,即把所有的fastbins清空,同時(shí)把相鄰的塊合并起來,等到所有的fastbins清空的時(shí)候,所有的塊也被合并起來了,然后調(diào)用free(2000)的時(shí)候,這塊將被合并起來,成為topchunk,并且大小遠(yuǎn)小于64K,所有堆將會(huì)壓縮,內(nèi)存歸還給系統(tǒng)。
?
解決方法:
減小mmap分配閾值,對(duì)于大內(nèi)存塊分配盡量采用mamp系統(tǒng)調(diào)用直接向操作系統(tǒng)分配,回收時(shí)用munmap返回給操作系統(tǒng)。但是這種做法會(huì)降低ptmalloc的分配釋放效率,因?yàn)橄到y(tǒng)調(diào)用mamp是串行的,操作系統(tǒng)需要對(duì)mmap分配內(nèi)存加鎖,而且操作系統(tǒng)對(duì)mmap的物理頁強(qiáng)制清0很慢。這個(gè)可以通過修改 MALLOC_MMAP_THRESHOLD_環(huán)境變量或者調(diào)用mallopt()接口來實(shí)現(xiàn)。
總結(jié)
以上是生活随笔為你收集整理的glibc(ptmalloc)内存暴增问题解决的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GPU 2012年10月 性能排名
- 下一篇: Linux下glibc内存管理