glic內存管理ptmalloc之原理 概述

3.概述

3.1內存管理一般性描述

當不知道程序的每個部分將需要多少內存時,系統內存空間有限,而內存需求又是變化的,這時就需要內存管理程序來負責分配和回收內存。程序的動態性越強,內存管理就越重要,內存分配程序的選擇也就更重要。

內存管理的方法

3.3.1.C 風格的內存管理程序

C 風格的內存管理程序主要實現 malloc()和 free()函數。內存管理程序主要通過調用 brk()或者 mmap()進程添加額外的虛擬內存。Doug  Lea  Malloc,ptmalloc,BSD  malloc,Hoard,TCMalloc 都屬於這一類內存管理程序

基於 malloc()的內存管理器仍然有很多缺點,不管使用的是哪個分配程序。對於那些需要保持長期存儲的程序使用 malloc()來管理內存可能會非常令人失望。

如果有大量的不固定的內存引用,經常難以知道它們何時被釋放。生存期侷限於當前函數的內存非常容易管理, 但是對於生存期超出該範圍的內存來說,管理

內存則困難得多。因爲管理內存的問題,很多程序傾向於使用它們自己的內存管理規則。

 

2.池式內存管理

內存池是一種半內存管理方法。內存池幫助某些程序進行自動內存管理,這些程序會經歷一些特定的階段,而且每個階段中都有分配給進程的特定階段的內存

例如,很多網絡服務器進程都會分配很多針對每個連接的內存——內存的最大生存期限爲當前連接的存在期。

Apache  使用了池式內存(pooled memory),將其連接拆分爲各個階段,每個階段都有自己的內存池。在結束每個階段時,會一次釋放所有內存。在池式內存管理中,每次內存分配都會指定內存池,從中分配內存。每個內存池都有不同的生存期限。在 Apache 中,有一個持續時間爲服務器存在期的內存池,還有一個持續時間爲連接的存在期的內存池,以及一個持續時間爲請求的存在期的池,另外還有其他一些內存池。因此,如果我的一系列函數不會生成比連接持續時間更長的數據,那麼我就可以完全從連接池中分配內存,並知道在連接結束時,這些內存會被自動釋放。另外,有一些實現允許註冊清除函數(cleanup functions),在清除內存池之前,恰好可以調用它,來完成在內存被清理前需要完成的其他所有任務(類似於面向對象中的析構函數)。

使用池式內存分配的優點如下所示:

l應用程序可以簡單地管理內存。

l內存分配和回收更快,因爲每次都是在一個池中完成的。分配可以在 O(1)時間內完成,釋放內存池所需時間也差不多(實際上是 O(n)時間,不過在大部分情況

下會除以一個大的因數,使其變成 O(1))。

l可以預先分配錯誤處理池(Error-handling  pools),以便程序在常規內存被耗盡時仍可以恢復。

l有非常易於使用的標準實現。池式內存的缺點是:

l內存池只適用於操作可以分階段的程序。

l內存池通常不能與第三方庫很好地合作。

l如果程序的結構發生變化,則不得不修改內存池,這可能會導致內存管理系統的重新設計。

您必須記住需要從哪個池進行分配。另外,如果在這裏出錯,就很難捕獲該內存池。

3.引用計數

在引用計數中,所有共享的數據結構都有一個域來包含當前活動“引用”結構的次數。當向一個程序傳遞一個指向某個數據結構指針時,該程序會將引用計數增加 1。實質上,是在告訴數據結構,它正在被存儲在多少個位置上。然後,當進程完成對它的使用後,該程序就會將引用計數減少 1。結束這個動作之後,它還會檢查計數是否已經減到零。如果是,那麼它將釋放內存。

4.垃圾收集

垃圾收集(Garbage collection)是全自動地檢測並移除不再使用的數據對象。垃圾收集器通常會在當可用內存減少到少於一個具體的閾值時運行。常,它們以程序所知的可用的一組“基本”數據——棧數據、全局變量、寄存器——作爲出發點。然後它們嘗試去追蹤通過這些數據連接到每一塊數據。收集器找到的都是有用的數據;它沒有找到的就是垃圾,可以被銷燬並重新使用這些無用的數據。爲了有效地管理內存,很多類型的垃圾收集器都需要知道數據結構內部指針的規劃,所以,爲了正確運行垃圾收集器,它們必須是語言本身的一部分。

垃圾收集的一些優點:

l永遠不必擔心內存的雙重釋放或者對象的生命週期。

l使用某些收集器,您可以使用與常規分配相同的 API。其缺點包括:

l使用大部分收集器時,您都無法干涉何時釋放內存。

l在多數情況下,垃圾收集比其他形式的內存管理更慢。

l垃圾收集錯誤引發的缺陷難於調試。

l如果您忘記將不再使用的指針設置爲 null,那麼仍然會有內存泄漏。

3.2Ptmalloc 內存管理概述

3.2.1簡介

Linux 中 malloc 的早期版本是由 Doug Lea 實現的,它有一個重要問題就是在並行處理時多個線程共享進程的內存空間,各線程可能併發請求內存,在這種情況下應該如何保證分配和回收的正確和高效。Wolfram Gloger 在 Doug Lea 的基礎上改進使得 Glibc 的 malloc 可以支持多線程——ptmalloc,在glibc-2.3.x.中已經集成了ptmalloc2,這就是我們平時使用的malloc,目前 ptmalloc 的最新版本 ptmalloc3。ptmalloc2 的性能略微比 ptmalloc3 要高一點點。ptmalloc 實現了 malloc(),free()以及一組其它的函數. 以提供動態內存管理的支持。分配器處在用戶程序和內核之間,它響應用戶的分配請求,向操作系統申請內存,然後將其返回給用戶程序,爲了保持高效的分配,分配器一般都會預先分配一塊大於用戶請求的內存, 並通過某種算法管理這塊內存。來滿足用戶的內存分配要求,用戶釋放掉的內存也並不是立即就返回給操作系統,相反,分配器會管理這些被釋放掉的空閒空間,以應對用戶以後的內存分配要求。也就是說,分配器不但要管理已分配的內存塊,還需要管理空閒的內存塊,當響應用戶分配要求時,分配器會首先在空閒空間中尋找一塊合適的內存給用戶,在空閒空間中找不到的情況下才分配一塊新的內存。爲實現一個高效的分配器,需要考慮很多的因素。比如,分配器本身管理內存塊所佔用的內存空間必須很小,分配算法必須要足夠的快。

3.2.3 內存管理數據結構概述

Glibc 的 malloc 可以支持多線程,由只有一個主分配區(main arena)增加了非主分配區(non main arena)支持,主分配區與非主分配區用環形鏈表進行管理。每一個分配區利用互斥鎖(mutex)使線程對於該分配區的訪問互斥。

每個進程只有一個主分配區,但可能存在多個非主分配區,ptmalloc 根據系統對分配區的爭用情況動態增加非主分配區的數量,分配區的數量一旦增加,就不會再減少了。

主分配區可以訪問進程的 heap 區域和 mmap 映射區域,也就是說主分配區可以使用 sbrk 和 mmap向操作系統申請虛擬內存。

非主分配區只能訪問進程的 mmap 映射區域,非主分配區每次使用 mmap()向操作系統“批發”HEAP_MAX_SIZE(32 位系統上默認爲 1MB,64 位系統默認爲 64MB)大小的虛擬內存,當用戶向非主分配區請求分配內存時再切割成小塊“零售”出去,畢竟系統調用是相對低效的,直接從用戶空間分配內存快多了。所以 ptmalloc 在必要的情況下才會調用 mmap()函數向操作系統申請虛擬內存。

當某一線程需要調用 malloc()分配內存空間時,該線程先查看線程私有變量中是否已經存在一個分配區,如果存在,嘗試對該分配區加鎖,如果加鎖成功,使用該分配區分配內存,如果失敗,該線程搜索循環鏈表試圖獲得一個沒有加鎖的分配區。如果所有的分配區都已經加鎖,那麼 malloc()會開闢一個新的分配區,把該分配區加入到全局分配區循環鏈表並加鎖,然後使用該分配區進行分配內存操作。在釋放操作中,線程同樣試圖獲得待釋放內存塊所在分配區的鎖,如果該分配區正在被別的線程使用,則需要等待直到其他線程釋放該分配區的互斥鎖之後纔可以進行釋放操作。

申請小塊內存時會產生很多內存碎片,ptmalloc 在整理時也需要對分配區做加鎖操作。

每個加鎖操作大概需要 5~10 個 cpu 指令,而且程序線程很多的情況下,鎖等待的時間就會延長,導致 malloc 性能下降。一次加鎖操作需要消耗 100ns 左右,正是鎖的緣故,導致 ptmalloc在多線程競爭情況下性能遠遠落後於 tcmalloc最新版的 ptmalloc 對鎖進行了優化,加入了PER_THREAD 和 ATOMIC_FASTBINS 優化,但默認編譯不會啓用該優化,這兩個對鎖的優化應該能夠提升多線程內存的分配的效率。

3.2.3.2 chunk 的組織

不管內存是在哪裏被分配的,用什麼方法分配,用戶請求分配的空間在 ptmalloc 中都使用一個 chunk 來表示。用戶調用 free()函數釋放掉的內存也並不是立即就歸還給操作系統,相反,它們也會被表示爲一個 chunk,ptmalloc 使用特定的數據結構來管理這些空閒的 chunk。

1.Chunk 格式

ptmalloc 在給用戶分配的空間的前後加上了一些控制信息,用這樣的方法來記錄分配的信息,以便完成分配和釋放工作。一個使用中的 chunk(使用中,就是指還沒有被 free 掉)

空閒 chunk 在內存中的結構

  1. chunk 中的空間複用????

爲了使得 chunk 所佔用的空間最小,ptmalloc 使用了空間複用,一個 chunk 或者正在

被使用,或者已經被 free 掉,所以 chunk 的中的一些域可以在使用狀態和空閒狀態表示不同的意義,來達到空間複用的效果。

3.2.3.3 空閒 chunk 容器

用戶 free 掉的內存並不是都會馬上歸還給系統,ptmalloc 會統一管理 heap 和 mmap 映射區域中的空閒的 chunk,當用戶進行下一次分配請求時,ptmalloc 會首先試圖在空閒的chunk 中挑選一塊給用戶,這樣就避免了頻繁的系統調用,降低了內存分配的開銷。 ptmalloc將相似大小的 chunk 用雙向鏈表鏈接起來,這樣的一個鏈表被稱爲一個 bin。Ptmalloc 一共維護了 128 個 bin,並使用一個數組(bins)來存儲這些 bin(如下圖所示)。

bins是空閒堆中的基本數據結構,他們被用來保存空閒堆塊。

Small bin:數組中從 2 開始編號的前 64 個 bin 稱爲 small bins,同一個small bin中的chunk具有相同的大小。兩個相鄰的small bin中的chunk大小相差8bytes。small bins 中的 chunk 按照最近使用順序進行排列,最後釋放的 chunk 被鏈接到鏈表的頭部,而申請 chunk 是從鏈表尾部開始,這樣,每一個 chunk 都有相同的機會被 ptmalloc 選中.

Large binsSmall bins 後面的 bin 被稱作 large bins。large bins 中的每一個 bin 分別包含了一個給定範圍內的 chunk,其中的 chunk 按大小序排列相同大小的 chunk 同樣按照最近使用順序排列。ptmalloc 使用“smallest-first,best-fit”原則在空閒 large bins 中查找合適的 chunk。

Fast bins:引入了 fast bins,不大於 max_fast (默認值爲 64B)的 chunk 被釋放後,首先會被放到 fast bins 中,fast bins 中的 chunk 並不改變它的使用標誌 P。這樣也就無法將它們合併,當需要給用戶分配的 chunk 小於或等於 max_fast 時,ptmalloc 首先會在 fast bins 中查找相應的空閒塊,然後纔會去查找bins中的空閒chunk。【LIFO的棧,使用單向鏈表實現。】

Unsorted Bin:unsorted bin 的隊列使用 bins 數組的第一個,如果被用戶釋放的 chunk 大於 max_fast,或者 fast bins 中的空閒 chunk 合併後,這些 chunk 首先會被放到 unsorted bin 隊列中,在進行 malloc 操作的時候,如果在 fast bins 中沒有找到合適的 chunk,則 ptmalloc 會先在 unsorted bin 中查找合適的空閒 chunk,然後才查找 bins。如果 unsorted bin 不能滿足分配要求。malloc便會將 unsorted bin 中的 chunk 加入 bins 中。然後再從 bins 中繼續進行查找和分配過程。unsorted bin 可以看做是 bins 的一個緩衝區,並不是所有的 chunk 都按照上面的方式來組織,實際上,有三種例外情況。Top chunk,mmaped chunk 和 last remainder.

  1. Top chunk:因爲內存是按地址從低向高進行分配的,在空閒內存的最高處,必然存在着一塊空閒 chunk,叫做 top chunk.

對於非主分配區預先從 mmap 區域分配一塊較大的空閒內存模擬 sub-heap,通過管理 sub-heap 來響應用戶的需求,當 bins 和 fast bins 都不能滿足分配需要的時候,ptmalloc 會設法在 top chunk 中分出一塊內存給用戶,如果 top chunk 本身不夠大,分配程序會重新分配一個 sub-heap,並將 top chunk 遷移到新的 sub-heap 上,新的 sub-heap與已有的 sub-heap 用單向鏈表連接起來,然後在新的 top chunk 上分配所需的內存以滿足分配。top chunk 在分配時總是在 fast bins 和 bins 之後被考慮。。Top chunk 的大小是隨着分配和回收不停變換的,如果從 top chunk 分配內存會導致 top chunk 減小,如果回收的 chunk 恰好與 top chunk 相鄰,那麼這兩個 chunk 就會合併成新的 top chunk,從而使 top chunk 變大。如果在 free 時回收的內存大於某個閾值,並且 top chunk 的大小也超過了收縮閾值,ptmalloc會收縮 sub-heap,如果 top-chunk 包含了整個 sub-heap,ptmalloc 會調用 munmap 把整個sub-heap 的內存返回給操作系統。

由於主分配區是唯一能夠映射進程 heap 區域的分配區,它可以通過 sbrk()來增大或是收縮進程 heap 的大小ptmalloc 在開始時會預先分配一塊較大的空閒內存(也就是所謂的 heap),主分配區的 top chunk 在第一次調用 malloc 時會分配一塊(chunk_size + 128KB) align 4KB 大小的空間作爲初始的 heap,用戶從 top chunk 分配內存時,可以直接取出一塊內存給用戶。在回收內存時,回收的內存恰好與 top chunk 相鄰則合併成新的 top chunk,當該次回收的空閒內存大小達到某個閾值,並且 top chunk 的大小也超過了收縮閾值,會執行內存收縮,減小 top chunk 的大小,但至少要保留一個頁大小的空閒內存,從而把內存歸還給操作系統。如果向主分配區的 top chunk 申請內存,而 top chunk 中沒有空閒內存,ptmalloc會調用 sbrk()將的進程 heap 的邊界 brk 上移,然後修改 top chunk 的大小。

  1. Mmaped chunk:當需要分配的 chunk 足夠大,而且 fast bins 和 bins 都不能滿足要求,甚至 top chunk 本身也不能滿足分配需求時,ptmalloc 會使用 mmap 來直接使用內存映射來將頁映射到進程空間。這樣分配的 chunk 在被 free 時將直接解除映射,於是就將內存歸還給了操作系統,再次對這樣的內存區的引用將導致 segmentation fault 錯誤。

3.2.3.4 sbrk 與 mmap

.bss 段之上的這塊分配給用戶程序的空間被稱爲 heap (堆)。start_brk 指向 heap 的開始,而 brk 指向 heap 的頂部。可以使用系統調用 brk()和 sbrk()來增加標識 heap 頂部的 brk 值,從而線性的增加分配給用戶的 heap 空間。在使 malloc 之前,brk的值等於start_brk,也就是說heap大小爲0。ptmalloc在開始時,若請求的空間小於 mmap分配閾值(mmap threshold,默認值爲 128KB)主分配區會調用 sbrk()增加一塊大小爲 (128 KB + chunk_size) align 4KB 的空間作爲 heap。非主分配區會調用 mmap 映射一塊大小爲HEAP_MAX_SIZE(32 位系統上默認爲 1MB,64 位系統上默認爲 64MB)的空間作爲 sub-heap

當用戶請求內存分配時,首先會在這個區域內找一塊合適的 chunk 給用戶。當用戶釋放了 heap 中的 chunk 時,ptmalloc 又會使用 fastbins 和 bins 來組織空閒 chunk。若需要分配的 chunk 大小小於 mmap分配閾值,而 heap 空間又不夠,則此時主分配區會通過 sbrk()調用來增加 heap 大小,非主分配區會調用 mmap 映射一塊新的 sub-heap,也就是增加 top chunk 的大小,每次 heap 增加的值都會對齊到 4KB.

當用戶的請求超過 mmap 分配閾值,並且主分配區使用 sbrk()分配失敗的時候,或是非主分配區在 top chunk 中不能分配到需要的內存時ptmalloc 會嘗試使用 mmap()直接映射一塊內存到進程內存空間。使用 mmap()直接映射的 chunk 在釋放時直接解除映射,而不再屬於進程的內存空間。任何對該內存的訪問都會產生段錯誤。

內存分配概述:

1. 分配算法概述,以 32 系統爲例,64 位系統類似。

l 小於等於 64 字節:用 pool 算法分配。

l 64 到 512 字節之間:在最佳匹配算法分配和 pool 算法分配中取一種合適的。

l 大於等於 512 字節:用最佳匹配算法分配。

l 大於等於 mmap 分配閾值(默認值 128KB):根據設置的 mmap 的分配策略進行分配,

如果沒有開啓 mmap 分配閾值的動態調整機制,大於等於 128KB 就直接調用 mmap分配。否則,大於等於 mmap 分配閾值時才直接調用 mmap()分配。

ptmalloc 的響應用戶內存分配要求的具體步驟爲:

1) 獲取分配區的鎖,爲了防止多個線程同時訪問同一個分配區,在進行分配之前需要

取得分配區域的鎖。線程先查看線程私有實例中是否已經存在一個分配區,如果存

在嘗試對該分配區加鎖,如果加鎖成功,使用該分配區分配內存,否則,該線程搜

索分配區循環鏈表試試圖獲得一個空閒(沒有加鎖)的分配區。如果所有的分配區都

已經加鎖,那麼 ptmalloc 會開闢一個新的分配區,把該分配區加入到全局分配區循

環鏈表和線程的私有實例中並加鎖,然後使用該分配區進行分配操作。開闢出來的

新分配區一定爲非主分配區,因爲主分配區是從父進程那裏繼承來的。開闢非主分

配區時會調

  1. 將用戶的請求大小轉換爲實際需要分配的 chunk 空間大小
  2. 判斷所需分配chunk的大小是否滿足chunk_size <= max_fast (max_fast 默認爲 64B),如果是的話首先嚐試在 fast bins 中取一個所需大小的 chunk 分配給用戶。如果可以找到,則分配結束否則轉第4步,
  3. 判斷所需大小是否處在 small bins 中,即判斷 chunk_size < 512B 是否成立。如果chunk 大小處在 small bins 中。 根據所需分配的 chunk 的大小,找到具體所在的某個 small bin,從該 bin 的尾部摘取一個恰好滿足大小的 chunk。若成功,則分配結束。否則轉第5步,
  4. 這說明需要分配的是一塊大的內存,或者 small bins 中找不到合適的chunk。於是,ptmalloc 首先會遍歷 fast bins 中的 chunk,將相鄰的 chunk 進行合併,並鏈接到 unsorted bin 中,然後遍歷 unsorted bin 中的 chunk,如果 unsorted bin 只有一個 chun並且這個 chunk 在上次分配時被使用過,並且所需分配的 chunk 大小屬於 small bins,並且 chunk 的大小大於等於需要分配的大小,這種情況下就直接將該 chunk 進行切割,分配結束。否則轉入第6步
  5. 這說明需要分配的是一塊大的內存,或者 small bins 和 unsorted bin 中都找不到合適的 chunk,並且 fast bins 和 unsorted bin 中所有的 chunk 都清除乾淨了。根據 chunk 的空間大小將其放入 small bins 或是 large bins 中,遍歷完成後,從 large bins 中按照“smallest-first,best-fit”原則,找一個合適的 chunk,從中劃分一塊所需大小的 chunk,並將剩下的部分鏈接回到 bins 中。若操作成功,則分配結束否則轉入第7步,
  6. 如果搜索 fast bins 和 bins 都沒有找到合適的 chunk,那麼就需要操作 top chunk 來進行分配了。判斷 top chunk 大小是否滿足所需 chunk 的大小,如果是,則從 top chunk 中分出一塊來。轉第9步,否則轉入第8步
  7. 這說明 top chunk 也不能滿足分配要求,所以,於是就有了兩個選擇: 如果是主分配區,調用 sbrk(),增加 top chunk 大小;如果是非主分配區,調用 mmap來分配一個新的 sub-heap,增加 top chunk 大小;或者使用 mmap()來直接分配。我們判斷所需分配的 chunk大小是否大於等於 mmap 分配閾值,如果是的話,轉第9步,否則轉第10步增加 top chunk 的大小。
  8. 使用mmap 系統調用爲程序的內存空間映射一塊 chunk_size align 4kB 大小的空間。然後將內存指針返回給用戶。
  9. 判斷是否爲第一次調用 malloc,若是主分配區,則需要進行一次初始化工作,分配一塊大小爲(chunk_size + 128KB) align 4KB 大小的空間作爲初始的 heap。若已經初始化過了,主分配區則調用 sbrk()增加 heap 空間,分主分配區則在 top chunk 中切割出一個 chunk,使之滿足分配需求,並將內存指針返回給用戶

總結:根據用戶請求分配的內存的大小,ptmalloc 有可能會在兩個地方爲用戶分配內存空間。在第一次分配內存時,一般情況下只存在一個主分配區,但也有可能從父進程那裏繼承來了多個非主分配區,在這裏主要討論主分配區的情況,brk 值等於start_brk,所以實際上 heap 大小爲 0,top chunk 大小也也是 0。這時,如果不增加 heap大小,就不能滿足任何分配要求。所以,若用戶的請求的內存大小小於 mmap 分配閾值,則 ptmalloc 會初始 heap。然後在 heap 中分配空間給用戶,以後的分配就基於這個 heap進行。若第一次用戶的請求就大於 mmap 分配閾值,則 ptmalloc 直接使用 mmap()分配一塊內存給用戶,而 heap 也就沒有被初始化,直到用戶第一次請求小於 mmap 分配閾值的內存分配。第一次以後的分配就比較複雜了,簡單說來,ptmalloc 首先會查找 fast bins,如果不能找到匹配的 chunk,則查找 small bins。若還是不行,合併 fast bins,把 chunk加入 unsorted bin,在 unsorted bin 中查找,若還是不行,把 unsorted bin 中的 chunk 全加入 large bins 中,並查找 large bins。在 fast bins 和 small bins 中的查找都需要精確匹配,而在 large bins 中查找時,則遵循“smallest-firstt,best-fit”的原則,不需要精確匹配。若以上方法都失敗了,則 ptmalloc 會考慮使用 top chunk。若 top chunk 也不能滿足分配要求,而且所需 chunk 大小大於 mmap 分配閾值,則使用 mmap 進行分配。否則top chunk 也不能滿足分配要求,而且所需 chunk 大小不大於 mmap 分配閾值則調用 sbrk()將的進程 heap 的邊界 brk 上移來先增加heap,進而增大 top chunk;非主分區通過mmap分配新的sub-heap,即增加topchunk。以滿足分配要求

內存回收概述:

free() 函數接受一個指向分配區域的指針作爲參數,釋放該指針所指向的 chunk。而具

體的釋放方法則看該 chunk 所處的位置該 chunk 的大小

free()函數的工作步驟如下:

1) free()函數同樣首先需要獲取分配區的鎖,來保證線程安全。

2) 判斷傳入的指針是否爲 0,如果爲 0,則什麼都不做,直接 return否則轉下一步

3) 判斷所需釋放的 chunk 是否爲 mmaped chunk,如果是,則調用 munmap()釋放

mmaped chunk,解除內存空間映射,該該空間不再有效。如果開啓了 mmap 分配

閾值的動態調整機制,並且當前回收的 chunk 大小大於 mmap 分配閾值,將 mmap

分配閾值設置爲該 chunk 的大小,將 mmap 收縮閾值設定爲 mmap 分配閾值的 2

倍,釋放完成否則轉下一步

4) 判斷 chunk 的大小和所處的位置,若 chunk_size <= max_fast(64B),並且 chunk 並不位於heap 的頂部,也就是說並不與 top chunk 相鄰, 將 chunk 放到 fast bins 中,chunk 放入到 fast bins 中時,並不修改該 chunk 使用狀態位 P。也不與相鄰的 chunk 進行合併。只是放進去,如此而已。這一步做完之後釋放便結束了,程序從 free()函數中返回。否則轉下一步,

5)判斷前一個 chunk 是否處在使用中,如果前一個塊也是空閒塊,則合併。 判斷當前釋放 chunk 的下一個塊是否爲 top chunk,如果是, 說明釋放了一個與 top chunk 相鄰的 chunk。則無論它有多大,都將它與 top chunk 合併,並更新 top chunk 的大小等信息,並轉第7步。如果不是topchunk否則轉下一步,

6)判斷下一個 chunk 是否處在使用中,如果下一個 chunk 也是空閒的,則合併,並將合併後的 chunk 放到 unsorted bin 中。注意,這裏在合併的過程中,要更新 chunk的大小,以反映合併後的 chunk 的大小。

7)判斷合併後的 chunk(5或6) 的大小是否大於 FASTBIN_CONSOLIDATION_THRESHOLD(默認64KB),如果是的話,則會觸發進行 fast bins 的合併操作,fast bins 中的 chunk 將被遍歷,並與相鄰的空閒 chunk 進行合併,合併後的 chunk 會被放到 unsorted bin 中。fast bins 將變爲空。

8)再判斷 top chunk 的大小是否大於 mmap 收縮閾值(默認爲 128KB),【內存收縮】如果是的話,對於主分配區,則會試圖歸還 top chunk 中的一部分給操作系統。但是最先分配的128KB 空間是不會歸還的,ptmalloc 會一直管理這部分內存,用於響應用戶的分配請求;如果爲非主分配區,會進行 sub-heap 收縮,將 top chunk 的一部分返回給操作系統,如果 top chunk 爲整個 sub-heap,會把整個 sub-heap 還回給操作系統。 釋放結束!

 

3.2.6 配置選項概述

1. M_MXFAST

M_MXFAST 用於設置 fast bins 中保存的 chunk 的最大大小,默認值爲 64B,fast bins 中保存的 chunk 在一段時間內不會被合併,分配小對象時可以首先查找 fast bins,如果 fast bins找到了所需大小的 chunk,就直接返回該 chunk,大大提高小對象的分配速度,但這個值設置得過大,會導致大量內存碎片,並且會導致 ptmalloc 緩存了大量空閒內存,去不能歸還給操作系統,導致內存暴增

M_MXFAST 的最大值爲 80B,不能設置比 80B 更大的值,因爲設置爲更大的值並不能提高分配的速度。Fast bins 是爲需要分配許多小對象的程序設計的,

如果設置該選項爲 0,就會不使用 fast bins。

2. M_TRIM_THRESHOLD

M_TRIM_THRESHOLD 用於設置 mmap 收縮閾值,默認值爲 128KB。自動收縮只會在 free時才發生,如果當前 free 的 chunk 大小加上前後能合併 chunk 的大小大於 64KB,並且 top chunk 的大小達到 mmap 收縮閾值,對於主分配區,調用 malloc_trim()返回一部分內存給操作系統,對於非主分配區,調用 heap_trim()返回一部分內存給操作系統,

  1. M_MMAP_THRESHOLD

M_MMAP_THRESHOLD 用於設置 mmap 分配閾值,默認值爲 128KB,ptmalloc 默認開啓動態調整 mmap 分配閾值和 mmap 收縮閾值。

當用戶需要分配的內存大於mmap分配閾值,ptmalloc的malloc()函數其實相當於mmap()的簡單封裝,free 函數相當於 munmap()的簡單封裝。相當於直接通過系統調用分配內存,

回收的內存就直接返回給操作系統了。因爲這些大塊內存不能被 ptmalloc 緩存管理,不能重用,所以 ptmalloc 也只有在萬不得已的情況下才使用該方式分配內存。

但mmap分配也由於好處:

1.Mmap 的空間可以獨立從系統中分配和釋放的系統,對於長時間運行的程序,申請

長生命週期的大內存塊就很適合有這種方式。

  1. Mmap 的空間不會被 ptmalloc 鎖在緩存的 chunk 中,不會導致 ptmalloc 內存暴增的問題。

壞處有:

  1. 該內存不能被 ptmalloc 回收再利用。
  2. 會導致更多的內存浪費,因爲 mmap 需要按頁對齊。
  3. 分配效率跟操作系統提供的 mmap()函數的效率密切相關,Linux 系統強制把匿

名 mmap 的內存物理頁清 0 是很低效的。

用 mmap 來分配長生命週期的大內存塊就是最好的選擇,其他情況下都不太高效。

4.M_MMAP_MAX

M_MMAP_MAX 用於設置進程中用 mmap 分配的內存塊的最大限制,默認值爲 64K,因爲有些系統用 mmap 分配的內存塊太多會導致系統的性能下降。

如果將 M_MMAP_MAX 設置爲 0,ptmalloc 將不會使用 mmap 分配大塊內存。

mmap 分配閾值動態調整機制:

當 ptmalloc munmap chunk 時,如果回收的 chunk 空間大小大於 mmap 分配閾值的當前值,並且小於 DEFAULT_MMAP_THRESHOLD_MAX(32 位系統默認爲 512KB,64 位系統默認爲 32MB),ptmalloc 會把 mmap 分配閾值調整爲當前回收的 chunk 的大小,並將 mmap 收縮閾值(mmap trim threshold)設置爲 mmap 分配閾值的 2 倍。這就是 ptmalloc 的對 mmap分配閾值的動態調整機制,該機制是默認開啓的,當然也可以用 mallopt()關閉該機制

 

爲了避免內存暴增,使用應注意幾點:

1.後分配的內存先釋放。ptmalloc 的內存收縮是從 top chunk 開始,如果與 top chunk 相鄰的那個 chunk 在我們 NoSql 的內存池中沒有釋放,top chunk 以下的空閒內存都無法返回給系統,即使這些空閒內存有幾十個 G 也不行。

2.Ptmalloc 不適合用於管理長生命週期的內存,這將導致 ptmalloc 內存暴增。如果要用 ptmalloc 分配長週期內存,在 32 位系統上,分配的內存塊最好大於 1MB。這是由於 ptmalloc 默認開啓 mmap 分配閾值動態調整功能,1MB 是 32 位系統 mmap 分配閾值的最大值,這樣可以保證 ptmalloc 分配的內存一定是從 mmap 映射區域分配的,當 free 時,ptmalloc 會直接把該內存返回給操作系統,避免了被 ptmalloc 緩存。

3.不要關閉 ptmalloc 的 mmap 分配閾值動態調整機制。因爲這種機制保證了短生命週期的內存分配儘量從 ptmalloc 緩存的內存 chunk 中分配,更高效,浪費更少的內存。如果關閉了該機制,對大於 128KB 的內存分配就會使用系統調用 mmap 向操作系統分配內存,使用系統調用分配內存一般會比從 ptmalloc 緩存的 chunk 中分配內存慢,特別是在多線程同時分配大內存塊時,操作系統會串行調用 mmap(),併爲發生缺頁異常的頁加載新物理頁時,默認強制清 0。頻繁使用 mmap 向操作系統分配內存是相當低效的。使用mmap 分配的內存只適合長生命週期的大內存塊

4,儘量減少程序的線程數量和避免頻繁分配/釋放內存。Ptmalloc 在多線程競爭激烈的情況下,首先查看線程私有變量是否存在分配區,如果存在則嘗試加鎖,如果加鎖不成功會嘗試其它分配區,如果所有的分配區的鎖都被佔用着,就會增加一個非主分配區供當前線程使用。由於在多個線程的私有變量中可能會保存同一個分配區,所以當線程較多時,加鎖的代價就會上升,ptmalloc 分配和回收內存都要對分配區加鎖,從而導致了多線程競爭環境下 ptmalloc 的效率降低。

5.防止內存泄露,ptmalloc 對內存泄露是相當敏感的,根據它的內存收縮機制,如果與 top chunk 相鄰的那個 chunk 沒有回收,將導致 top chunk 一下很多的空閒內存都無法返回給操作系統

6.防止程序分配過多內存,或是由於 Glibc 內存暴增,導致系統內存耗盡,程序因 OOM 被系 統 殺

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