首頁  >  文章  >  後端開發  >  php記憶體管理詳解

php記憶體管理詳解

小云云
小云云原創
2018-03-09 14:14:032455瀏覽

 在C中直接使用malloc、free進行記憶體的分配和釋放,但頻繁的分配和釋放記憶體會產生記憶體碎片、降低系統效能,php的變數的分配和釋放會非常的頻繁、若直接通過malloc的方式愛進行分配、會造成嚴重的性能問題,作為語言級的應用、這種損耗不太能接受、所以php實現了自己的內存池ZendMM,來替代glibc的malloc和free、以解決內存頻繁分配、釋放的問題。
 它定義了chunk、page、slot三種粒度的記憶體操作,每個chunk的大小為2MB、每個page 4kB,一個chunk切割成512個page、每個page切割成若干個slot,申請內存時、依申請的大小、執行不同的分配策略
 huge:大於2MB,直接呼叫系統分配,分配若干個chunk
 large:申請記憶體大於3092B(3/4page)、小於2044kb(511page)分配若干個page
 small:申請記憶體小於3092B,記憶體池提前定義了30種不同大小的記憶體(8、16、32...、3072),它們分佈在不同的page上、申請記憶體時直接在對應的記憶體上尋找對應的slot,記憶體池透過zend_mm_heap結構儲存記憶體池的主要訊息,例如大記憶體鍊錶、chunk鍊錶、slot各大小記憶體鍊錶等
 大記憶體分配的其實是若干個chunk,接著透過一個zend_mm_huge_list結構進行管理,大內存之間構成單向鍊錶
 chunk是內存池向系統申請、釋放內存的最小粒度,chunk之間構成雙向鍊錶,第一個chunk的地址保存於zend_mm_heap- >main_chunk,第一個page的記憶體用來保存chunk自己的結構體成員,例如前後chunk的指標、目前chunk各page的使用情況等
 相同大小的slot之間構成單鍊錶

 記憶體池的初始化在php_module_startup階段完成、初始化過程主要是分配heap結構,這個過程在start_memory_manager過程完成,如果是多執行緒環境,則會為每一個執行緒分配一個記憶體池,執行緒之間互不影響,初始化時會根據環境變數use_zend_alloc_huge_pages來設定是否開啟內存大頁,非線程安全環境下、分配的heap會保存到alloc_globals中也就是AG宏,需要注意的是,zend_mm_heap這個結構不是單獨分配的,嵌入在在 chunk結構體中,,因為chunk結構體佔用了一個page、但實際上它用不了那麼大的內存,為了盡可能利用空間、就把它放在了這裡

 內存分配:  1. huge超過2MB的內存分配,分配時,會將它對齊到n個chunk,還會分配一個zned_mm_huge_list結構,管理所有的huge內存,內存對齊的過程是內存池在申請後自己調整的,而不是簡單的由作業系統來完成,會先按照實際大小申請一次,如果剛好可以達到內存對齊、無需調整、直接返回使用,如果不是ZEND_MM_CHUNK_SIZE(2MB)的整數倍,則zendMM會釋放掉這塊兒內存,然後按照實際記憶體大小+ZEND_MM_CHUNK_SIZE再申請一次,多申請的那塊是用來調整的 2. large分配:申請的記憶體大小在3072B(3/4page)和2044k(511個page)之間時,記憶體池會在chunk上尋找對應數量的page回,large的申請粒度是page,chunk上有free_map和map兩個成員用於記錄page的分配資訊
 free_map就是512bit,用來記錄該chunk上page的分配情況,已使用則置位1
 map用來記錄page的分配類型及分配的page頁數,每個page對應一個陣列成員,最高2位記錄page的分配類型、01是large、10是small,分配時從第一個chunk開始遍歷、依序查找chunk是否有滿足要求的page,若當前chunk沒有合適的、則查找下一個chunk,若直到最後都沒有合適的、則重新分配一個chunk,申請時,不知誰查找到足夠頁數的page、而是盡可能的填滿chunk的空隙,盡可能的與已分配的page連接在一起,避免中間出現page空隙(以減少後續分配時的查找次數)  1)從第一個chunk分組(0~63)開始檢查,目前分組有可用page、先偵測目前page的bit位元、找出第一個和最後一個空閒page的位置,如果不夠則把這些page都標記為1(已指派)、進行查找其它分組,如果page恰好、則直接使用、中斷檢索,如果page比需要的大、表示可用,但不是最優的,會接著查找其它chunk直到最後比較出來一個最優的(可最大程度利用page的)  2)找到合適的page後、就設定對應的page資訊、即free_map和map資訊然後回傳page位址 3. small分配:
 會先檢查對應規格的記憶體是否已分配、若未分配或分配已使用完、則申請相應頁數的page、page的分配過程與large分配一致,申請到page之後,按照固定大小切割成slot、slot之間用單鍊錶連接,鍊錶頭部保存至AG(mm_heap)->free_slot

#記憶體池釋放的粒度是chunk,透過efree來完成1. huge記憶體的釋放、large、smal類型的因為chunk的第一個page被佔用、所以不可能是相對chunk的偏移量為0,由此區分chunk類型和large、small類型,釋放時,之間將佔用的chunk釋放、同時從AG鍊錶刪除2. large內存的釋放:若計算得到offset不為0,表示該地址是large或者small內存、然後根據offset算出是第幾個page、得到page之後可以從chunk->map得到page的分配類型、就可以釋放指定類型的記憶體了,large並不會直接釋放、而是將page的分配資訊置為未分配、若釋放後、發現該chunk下都是未分配的,則釋放chunk、釋放時優選選擇把chunk移到AG,緩存數達到一定值後就不再繼續緩存新加入的chunk、將內存歸還系統、避免佔用過多的內存,在分配chunk時若發現chached_chunks中有緩存的chunk直接取出使用、不向系統發出申請3. small類型的釋放、直接將釋放的slot插回該規則slot的可用鍊錶的頭部即可、比較簡單

 在C中直接使用malloc、free進行記憶體的分配和釋放,但頻繁的分配和釋放記憶體會產生記憶體碎片、降低系統效能,php的變數的分配和釋放會非常的頻繁、若直接通過malloc的方式愛進行分配、會造成嚴重的性能問題,作為語言級的應用、這種損耗不太能接受、所以php實現了自己的內存池ZendMM,來替代glibc的malloc和free、以解決內存頻繁分配、釋放的問題

 它定義了chunk、page、slot三種粒度的記憶體操作,每個chunk的大小為2MB、每個page 4kB,一個chunk切割成512個page、每個page切割成若干個slot,申請記憶體時、依照申請的大小、執行不同的分配策略
 huge:大於2MB,直接呼叫系統分配,分配若干個chunk
 large:申請記憶體大於3092B(3/4page) 、小於2044kb(511page)分配若干個page
 small:申請記憶體小於3092B,記憶體池提前定義好了30種不同大小的記憶體(8、16、32...、3072),它們分佈在不同的page上、申請記憶體時直接在對應的記憶體上尋找對應的slot,記憶體池透過zend_mm_heap結構儲存記憶體池的主要訊息,例如大記憶體鍊錶、chunk鍊錶、slot各大小記憶體鍊錶等
 大記憶體分配的實際上是若干個chunk,然後透過一個zend_mm_huge_list結構進行管理,大內存之間構成單向鍊錶
 chunk是內存池向系統申請、釋放內存的最小粒度,chunk之間構成雙向鍊錶,第一個chunk的位址保存於zend_mm_heap->main_chunk,第一個page的記憶體用來保存chunk自己的結構體成員,例如前後chunk的指標、目前chunk各page的使用情況等
 相同大小的slot之間構成單鍊錶

 記憶體池的初始化在php_module_startup階段完成、初始化過程主要是分配heap結構,這個過程在start_memory_manager過程完成,如果是多執行緒環境,則會為每個執行緒分配一個記憶體池,執行緒之間互不影響,初始化時會根據環境變數use_zend_alloc_huge_pages來設定是否開啟記憶體大頁,非執行緒安全環境下、分配的heap會儲存到alloc_globals中也就是AG宏,需要注意的是,zend_mm_heap這個結構不是單獨分配的,嵌入在chunk結構體中,因為chunk結構體佔用了一個page、但實際上它用不了那麼大的內存,為了盡可能利用空間、就把它放在了這裡

 內存分配:  1. huge超過2MB的內存分配,分配時,會將它對齊到n個chunk,還會分配一個zned_mm_huge_list結構,管理所有的huge內存,內存對齊的過程是內存池在申請後自己調整的,而不是簡單的由作業系統來完成,會先按照實際大小申請一次,如果剛好可以達到內存對齊、無需調整、直接返回使用,如果不是ZEND_MM_CHUNK_SIZE(2MB)的整數倍,則zendMM會釋放掉這塊兒內存,然後按照實際內存大小+ZEND_MM_CHUNK_SIZE再申請一次,多申請的那塊是用來調整的 2. large分配:申請的內存大小在3072B(3/4page)和2044k(511page)之間時,內存池會在chunk上查找對應數量的page返回,large的申請粒度是page,chunk上有free_map和map兩個成員用於記錄page的分配資訊
 free_map就是512bit,用來記錄該chunk上page的分配情況,已使用則置位1
 map用來記錄page的分配類型及分配的page頁數,每個page對應一個數組成員,最高2位記錄page的分配類型、01是large、10是small,分配時從第一個chunk開始遍歷、依次查找chunk是否有滿足要求的page,若當前chunk沒有合適的、則查找下一個chunk,若直到最後都沒有合適的、則重新分配一個chunk,申請時,不知誰查找到足夠頁數的page、而是盡可能的填滿chunk的空隙,盡可能的與已分配的page連接在一起,避免中間出現page空隙(以減少後續分配時的查找次數)  1)從第一個chunk分組(0~63)開始檢查,當前分組有可用page、先檢測當前page的bit位、找到第一個和最後一個空閒page的位置,如果不夠則把這些page都標記為1(已指派)、進行查找其它分組,如果page恰好、則直接使用、中斷檢索,如果page比需要的大、表示可用,但不是最優的,會接著查找其它chunk直到最後比較出來一個最優的(可最大程度利用page的)  2)找到合適的page後、就設定對應的page資訊、即free_map和map資訊然後回傳page位址 3. small分配:
 會先檢查對應規格的記憶體是否已分配、若未分配或分配已使用完、則申請相應頁數的page、page的分配流程與large分配一致,申請到page之後,按照固定大小切割成slot、slot之間用單鍊錶連接,鍊錶頭部保存至AG(mm_heap)->free_slot


#記憶體池釋放的粒度是chunk,透過efree來完成1. huge記憶體的釋放、large、smal類型的因為chunk的第一個page被佔用、所以不可能是相對chunk的偏移量為0,由此區分chunk類型和large、small類型,釋放時,之間將佔用的chunk釋放、同時從AG鍊錶刪除2. large內存的釋放:若計算得到offset不為0,表示該地址是large或者small內存、然後根據offset算出是第幾個page、得到page之後可以從chunk->map得到page的分配類型、就可以釋放指定類型的記憶體了,large並不會直接釋放、而是將page的分配資訊置為未分配、若釋放後、發現該chunk下都是未分配的,則釋放chunk、釋放時優選選擇把chunk移到AG,緩存數達到一定值後就不再繼續緩存新加入的chunk、將內存歸還系統、避免佔用過多的內存,在分配chunk時若發現chached_chunks中有緩存的chunk直接取出使用、不向系統發出申請3. small類型的釋放、直接將釋放的slot插回該規則slot的可用鍊錶的頭部即可、比較簡單。

相關推薦:

JS記憶體管理實例講解

#詳細介紹Linux中的記憶體管理

PHP變數與記憶體管理的學習筆記

以上是php記憶體管理詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn