記憶體申請

ZendMM使用自身heap層申請記憶體追蹤結果:

 ZEND_ASSIGN_SPEC_CV_CONST_HANDLER (......)
-> ALLOC_ZVAL(......)
-> ZEND_FAST_ALLOC(......)
-> emalloc (......)
-> _emalloc(......)
-> _zend_mm_alloc_int(.....)

  void *_emalloc 實現了對記憶體的申請操作,在_emalloc的處理過程中, 對是否使用ZendMM進行了判斷,如果heap層沒有使用ZendMM來管理, 就直接使用_zend_mm_heap結構中定義的_malloc函式進行記憶體的分配; (這裡的_malloc可以是malloc,win32,mmap_anon,mmap_zero中的一種);

  ZendMM進行記憶體管理流程

  1. 記憶體檢查。 對要申請的記憶體大小進行檢查,如果太大(超出memory_limit則報 Out of Memory);
  2. 如果命中快取,使用fastcache得到記憶體塊,然後直接進行第5步;
  3. 在ZendMM管理的heap層儲存中搜索合適大小的記憶體塊, 在這一步驟ZendMM通過與ZEND_MM_MAX_SMALL_SIZE進行大小比較, 把記憶體請求分為兩種型別: large和small。small型別的的請求會先使用zend_mm_low_bit函式 在mm_heap中的free_buckets中查詢,未找到則使用與large型別相同的方式: 使用zend_mm_search_large_block函式在“大塊”記憶體(_zend_mm_heap->large_free_buckets)中進行查詢。 如果還沒有可以滿足大小需求的記憶體,最後在rest_buckets中進行查詢。 也就是說,記憶體的分配是在三種列表中小到大進行的。 找到可以使用的block後,進行第5步;
  4. 如果經過第3步的查詢還沒有找到可以使用的資源(請求的記憶體過大),需要使用ZEND_MM_STORAGE_ALLOC函式向系統再申請一塊記憶體(大小至少為ZEND_MM_SEG_SIZE),然後直接將對齊後的地址分配給本次請求。跳到第6步;
  5. 使用zend_mm_remove_from_free_list函式將已經使用block節點在zend_mm_free_block中移除;
  6. 記憶體分配完畢,對zend_mm_heap結構中的各種標識型變數進行維護,包括large_free_buckets, peak,size等;
  7. 返回分配的記憶體地址;

  PHP對記憶體的分配,是結合PHP的用途來設計的,PHP一般用於web應用程式的資料支援, 單個指令碼的執行週期一般比較短(最多達到秒級),記憶體大塊整塊的申請,自主進行小塊的分配, 沒有進行比較複雜的不相臨地址的空閒記憶體合併,而是集中再次向系統請求。 這樣做的好處就是執行速度會更快,缺點是隨著程式的執行時間的變長, 記憶體的使用情況會“越來越多”(PHP5.2及更早版本)。 所以PHP5.3之前的版本並不適合做為守護程序長期執行。 在PHP5.3中引入了新的GC(垃圾回收)機制。

記憶體銷燬

  ZendMM在記憶體銷燬的處理上採用與記憶體申請相同的策略,當程式unset一個變數或者是其他的釋放行為時, ZendMM並不會直接立刻將記憶體交回給系統,而是隻在自身維護的記憶體池中將其重新標識為可用, 按照記憶體的大小整理到上面所說的三種列表(small,large,free)之中,以備下次記憶體申請時使用。

  記憶體銷燬的最終實現函式是_efree。在_efree中,記憶體的銷燬首先要進行是否放回cache的判斷。 如果記憶體的大小滿足ZEND_MM_SMALL_SIZE並且cache還沒有超過系統設定的ZEND_MM_CACHE_SIZE, 那麼,當前記憶體塊zend_mm_block就會被放回mm_heap->cache中。 如果記憶體塊沒有被放回cache,則使用下面的程式碼進行處理(Zend/zend_alloc.c):

        zend_mm_block *mm_block;//要銷燬的記憶體塊
zend_mm_block *next_block;
     size_t size;
     ...
next_block = ZEND_MM_BLOCK_AT(mm_block, size);
if (ZEND_MM_IS_FREE_BLOCK(next_block)) {
zend_mm_remove_from_free_list(heap, (zend_mm_free_block *) next_block);
size += ZEND_MM_FREE_BLOCK_SIZE(next_block);
}
if (ZEND_MM_PREV_BLOCK_IS_FREE(mm_block)) {
mm_block = ZEND_MM_PREV_BLOCK(mm_block);
zend_mm_remove_from_free_list(heap, (zend_mm_free_block *) mm_block);
size += ZEND_MM_FREE_BLOCK_SIZE(mm_block);
}
if (ZEND_MM_IS_FIRST_BLOCK(mm_block) &&
ZEND_MM_IS_GUARD_BLOCK(ZEND_MM_BLOCK_AT(mm_block, size))) {
zend_mm_del_segment(heap, (zend_mm_segment *) ((char *)mm_block - ZEND_MM_ALIGNED_SEGMENT_SIZE));
} else {
ZEND_MM_BLOCK(mm_block, ZEND_MM_FREE_BLOCK, size);
zend_mm_add_to_free_list(heap, (zend_mm_free_block *) mm_block);
}

  這段程式碼邏輯比較清晰,主要是根據當前要銷燬的記憶體塊mm_blockzend_mm_heap 雙向連結串列中所處的位置進行不同的操作。如果下一個節點還是free的記憶體,則將下一個節點合併; 如果上一相鄰節點記憶體塊為free,則合併到上一個節點; 如果只是普通節點,剛使用 zend_mm_add_to_free_list或者zend_mm_del_segment 進行回收。

  就這樣,ZendMM將記憶體塊以整理收回到zend_mm_heap的方式,回收到記憶體池中。 程式使用的所有記憶體,將在程序結束時統一交還給系統。