內存暴漲問題細探

1.  進程虛擬空間

進程虛擬空間如下圖:

  

如上圖示:最高的1G空間保留給內核使用。接下來是棧,棧向低地址方向延伸(棧的大小受RLIMIT_STACK限制,默認爲8M),下面是MMAP區(文件映射內存,如動態庫等,SPP微線程的私有棧也位於這裏),下面是堆(動態內存增長),堆向高地址方向延伸,接下來依次是BSS、數據段、代碼段。

2. Linux下動態內存分配實現機制

C、C++的動態內存分配、管理都是基於malloc和free的,動態內存即虛擬空間堆區。另外多說一句,malloc和free操作的也是虛擬地址空間。

malloc,動態內存分配函數。是通過brk(sbrk)和mmap這兩個系統調用實現的。

結合上文進程虛擬空間圖,brk(sbrk)是將數據段(.data)的最高地址指針_edata往高地址推。mmap是在進程的虛擬地址空間中(堆和棧中間,稱爲文件映射區域的地方)找一塊空閒的虛擬內存。這兩種實現方式的區別大致如下:

brk(sbrk),性能損耗少; mmap相對而言,性能損耗大

mmap不存在內存碎片(是物理頁對齊的,整頁映射和釋放); brk(sbrk)可能存在內存碎片(由於new和delete的順序不同,可能存在空洞,又稱爲碎片)

無論是通過brk(sbrk)還是mmap調用分配的內存都是虛擬空間的內存,只有在第一次訪問已分配的虛擬地址空間的時候,發生缺頁中斷,操作系統負責分配物理內存,然後建立虛擬內存和物理內存之間的映射關係。

delete,動態內存釋放函數。如果是brk(sbrk)分配的內存,直接調用brk(sbrk)並傳入負數,即可縮小Heap區的大小;如果是mmap分配的內存,調用munmap歸還內存。無論這兩種那種處理方式,都會立即縮減進程虛擬地址空間,並歸還未使用的物理內存給操作系統。

 

brk(sbrk)和mmap都是系統調用,如果程序中頻繁的進行內存的擴張和收縮,每次都直接調用,當然可以實現內存精確管理的目的,但是隨之而來的性能損耗也很顯著。目前大多數運行庫(glibc)等都對內存管理做了一層封裝,避免每次直接調用系統調用影響性能。如此,就涉及到運行庫的內存分配的算法問題了。

在標準C庫中,提供了malloc/free函數分配釋放內存,這兩個函數底層是由brk,mmap,munmap這些系統調用實現的。

3.缺頁中斷

如何查看進程發生缺頁中斷的次數?
用ps -o majflt,minflt -C program命令查看。
majflt代表major fault,中文名叫大錯誤,minflt代表minor fault,中文名叫小錯誤。這兩個數值表示一個進程自啓動以來所發生的缺頁中斷的次數。


發成缺頁中斷後,執行了那些操作?
當一個進程發生缺頁中斷的時候,進程會陷入內核態,執行以下操作:
1、檢查要訪問的虛擬地址是否合法
2、查找/分配一個物理頁
3、填充物理頁內容(讀取磁盤,或者直接置0,或者啥也不幹)
4、建立映射關係(虛擬地址到物理地址)
重新執行發生缺頁中斷的那條指令
如果第3步,需要讀取磁盤,那麼這次缺頁中斷就是majflt,否則就是minflt。

 

查看物理內存頁使用情況:cat /proc/$PID/smaps,裏面詳細記錄了該進程使用的物理頁內存情況,如Private_Dirty、Private_Clean等
mmap系統調用:讀寫MMAP映射區,相當於讀寫被映射的文件。本意是將文件當作內存一樣讀寫。相比Read、Write,減少了內存拷貝(Read、Write一個硬盤文件,需要先將數據從內核緩衝區拷貝到應用緩衝區(read),然後再將數據從應用緩衝區拷貝回內核緩衝區(write)。mmap直接將數據從內核緩衝區映拷貝到另一個內核緩衝區),但是被修改的數據從MMAP區同步到磁盤文件上,依賴於系統的頁管理算法,默認會慢條斯理得將內容寫到磁盤上。另外提供了msync強制同步到磁盤上。


4.Glibc內存分配算法

glibc的內存分配算法,是基於dlmalloc實現的ptmallocdlmalloc詳細可以參考A Memory Allocator或者Glibc內存分配器。這裏主要講下和內存歸還策略相關的,其他內容不做過多擴展。

整體來說,glibc採用的是dlmalloc。爲了避免頻繁調用系統調用,它內部維護了一個內存池,方便reuse,又稱爲free-listbins,如下圖示

 

 

所有調用delete釋放的內存,並不是立即調用brk(sbrk)歸還給操作系統,而是先將這個內存塊掛在free-list(bins)裏面,然後進行內存歸併(可選操作,相鄰的可用內存塊合併爲更大的可用內存塊),並檢查是否達到malloc_trim的threshhold,如果達到了,則調用malloc_trim歸還部分可用內存給操作系統。
glibc中,設置了默認進行malloc_trim的threshhold爲128K,也就是說當dlmalloc管理的內存池中最大可用內存>128K時,就會執行malloc_trim操作,歸還部分內存給操作系統;而在可用內存<=128K時,及時程序中delete了這部分內存,這些內存也是不會歸還給操作系統的。表現爲:調用delete之後,進程佔用的內存並沒有減少。

另外,部分glibc的默認設置如下:

DEFAULT_MXFAST             64 (for 32bit), 128 (for 64bit) // free-list(fastbin)最大內存塊 DEFAULT_TRIM_THRESHOLD     128 * 1024 // malloc_trim的門檻值 128k DEFAULT_TOP_PAD            0 DEFAULT_MMAP_THRESHOLD     128 * 1024 // 使用mmap分配內存的門檻值 128k DEFAULT_MMAP_MAX           65536 // mmap的最大數量

這些參數都可以通過mallopt進行調整。
malloc_trim(0)可以立即執行trim操作,將內存還給操作系統。
具體fastbin相關的內容,此處不做介紹,前期有很多基於fastbin的堆溢出攻擊,感興趣的同學可以google關鍵字fastbin搜索下。

 

5.考慮其他開源庫的解決方案

glibc大內存是128k,可以使用tcmalloc或者jemalloc來進行內存管理

tcmalloc是Google開源的一個內存管理庫

小對象(<=32K),大對象4k

jemalloc是facebook推出的

Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]

Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]

Huge: [4 MiB, 8 MiB, 12 MiB, …]

6.參考資料

摘自:一次"內存泄漏"引發的血案

 內存泄漏之malloc_trim

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章