歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

glibc(ptmalloc)內存暴增問題解決

直接上代碼:

  #include <stdio.h>

  #include <stdlib.h>

  #include <string.h>

  int main()

  {

  int alloc_time = 4000;

  char *a[alloc_time];

  char *b[alloc_time];

  int i, j;

  for(i=0; i<alloc_time; i++)

  {

  a[i] = (char *)malloc(52722);

  memset(a[i], 1, 52722);

  b[i] = (char *)malloc(1);

  memset(b[i], 1, 1);

  }

  printf("malloc finished\n");

  for(i=alloc_time-1; i>=0; i--)

  {

  free(a[i]);

  free(b[i]);

  }

  printf("free finished\n");

  //char *p = (char *)malloc(2000);

  //free(p);

  while(1){

  sleep(3);

  }

  }

  運行這個測試程序,發現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個隊列是不定長的(large bins),就是在一個范圍長度的都分配在一個隊列中。所有長度小於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是否為mmaped chunk,如果是,則調用munmap釋放mmaped chunk,解除內存空間映射,該空間不再有效,然後立刻返回;

  2. 如果和topchunk相鄰,直接和topchunk合並,不會放到其他的空閒隊列中取,然後立刻返回;

  3. 如果釋放的大小小於max_fast(80字節),就把它掛到fastbins中去返回,使用位仍然為1,當然更不會去合並相鄰塊,然後立刻返回;

  4. 如果釋放塊得大小介於80-128K,把chunk的使用位置為0,判斷前一個chunk是否處於使用中,如果前一塊也是空閒塊,則合並,並轉入下一步;

  5. 判斷當前釋放chunk的下一個塊是否為top chunk,如果是,則轉到第7步,否則轉下一步;

  6. 判斷下一個chunk是否處在使用中,如果也是空閒的,則合並,並將合並後的chunk掛到unsort隊列中去;

  7. 如果執行到了這一步,說明釋放了一個與top chunk相鄰的chunk;則無論它有多大,都將它與top chunk合並,並更新top chunk的大小等信息,轉下一步;

  8. 如果合並後的大小大於FASTBIN_CONSOLIDATION_THRESHOLD(64K),也會觸發consolidate,即fastbins的合並操作,合並後的chunk會被放到unsorted bin中,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,所有堆將會壓縮,內存歸還給系統。

Copyright © Linux教程網 All Rights Reserved