看本節前,希望讀者有linux內存分布的基本概念,可以閱讀這篇文章:
進程虛擬地址空間和函數調用棧
在本節中希望讀者可以一口氣閱讀完所有內容。
本博客內容全部來自小林coding:malloc 是如何分配內存的?
這里僅為筆記記錄。
文章目錄
- malloc是如何分配內存的?
- 通過 brk() 系統調用
- 通過mmap()申請內存
- ??malloc()分配的是物理內存嗎?
- ??malloc()竟然會構建內存池!
- free釋放內存,會馬上歸還給操作系統嗎?
- 即然 brk 那么好,為什么不全部使用brk來分配?
malloc是如何分配內存的?
首先我們要明確一點,malloc() 并不是系統調用,而是 C 庫里的函數,用于動態分配內存。
同時調用mlloc也并不一定會陷入內核態。
malloc申請內存會以兩種方式向操作系統申請堆內存:
- 通過 brk() 系統調用從堆分配內存
- 通過 mmap() 系統調用在文件映射區域分配內存
通過 brk() 系統調用
brk()的實現方式就是將「堆頂」指針向高地址移動,獲得新的內存空間。
并且malloc()源碼中默認定義了閾值,如果用戶分配內存小于128KB則通過brk()申請內存。
通過mmap()申請內存
mmap() 系統調用通過「私有匿名映射」的方式,在文件映射區分配一塊內存,也就是從文件映射區“偷”了一塊內存。如下圖:
在操作文件時,我們一般通過 open() 打開文件,然后使用 mmap() 將文件內容直接映射到虛擬內存上,這樣我們就可以像操作內存一樣操作文件的內容,這一塊區域就是文件映射區。
如果用戶分配的內存大于 128 KB,則通過 mmap() 申請內存;
不同的gilbc版本定義的閾值也是不同的
??malloc()分配的是物理內存嗎?
這個答案很明顯,malloc分配的是虛擬內存,但是下一句話更加重要!
如果分配后的虛擬內存沒有被訪問的話,虛擬內存是不會映射到物理內存的,這樣就不會占用物理內存了。
- 在訪問已分配的虛擬地址空間的時候,操作系統通過查找頁表,發現虛擬內存對應的頁沒有在物理內存中;
- 就會觸發缺頁中斷,
- 然后操作系統會建立虛擬內存和物理內存之間的映射關系。
??malloc()竟然會構建內存池!
malloc() 在分配內存的時候,會預分配更大的空間作為內存池。
也就是說,當我們調用brk()系統調用申請堆內存,仍然會為我們分配超過128KB字節的內存。即使你 malloc()的內存遠小于128k也是一樣。
為什么malloc要使用內存池來進行管理呢?
- 提高效率:內存池預先分配一大塊內存,然后在需要時從中分配小塊,減少了頻繁調用系統級別的內存分配開銷(brk()\mmap())「每次調用 sbrk 或 mmap 時,程序需要從用戶態切換到內核態,這個切換過程本身就有一定的開銷;陷入內核態也涉及到了系統需要更新內存管理的數據結構,如頁表和內存分配表,需要額外的時間;再一個,為了保證內存分配的線程安全,內核需要處理同步和鎖管理,增加了開銷」。
現在你還覺得每次調用malloc都會陷入內核態嗎?
- 減少了內存碎片,內存池通過組織和管理內存塊,能更好地利用內存空間,減少內存碎片,提高內存使用率。
- 快速釋放和重用,跟第一點一樣,在池內管理,避免了系統調用的高開銷。
free釋放內存,會馬上歸還給操作系統嗎?
看了malloc竟然開辟了內存池,你覺得free會把內存歸還給操作系統嗎?
這里直接說結論:
通過 free 釋放內存后,堆內存還是存在的,并沒有歸還給操作系統。當然,當進程退出后,操作系統就會回收進程的所有資源。
但是!
如果我們使用mmap方式申請內存,free釋放內存后就會馬上歸還操作系統。
總結:
- malloc 通過 brk() 方式申請的內存,free 釋放內存的時候,并不會把內存歸還給操作系統,而是緩存在 malloc 的內存池中,待下次使用;
- malloc 通過 mmap() 方式申請的內存,free 釋放內存的時候,會把內存歸還給操作系統,內存得到真正的釋放。
即然 brk 那么好,為什么不全部使用brk來分配?
我們考慮這樣一個場景:
如果我們連續申請了 10k,20k,30k 這三片內存,如果 10k 和 20k 這兩片釋放了,變為了空閑內存空間,如果下次申請的內存小于 30k,那么就可以重用這個空閑內存空間。

但是如果下次申請的內存大于 30k,沒有可用的空閑內存空間,必須向 OS 申請,實際使用內存繼續增大。
因此,隨著系統頻繁地 malloc 和 free ,尤其對于小塊內存,堆內將產生越來越多不可用的碎片,導致“內存泄露”。而這種“泄露”現象使用 valgrind 是無法檢測出來的。
所以,malloc 實現中,充分考慮了 brk 和 mmap 行為上的差異及優缺點,默認分配大塊內存 (128KB) 才使用 mmap 分配內存空間。