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