PHP 內存管理分為兩大層面:面向應用層的引用計數、寫時復制,及底層 Zend Memory Manager (Zend MM) 對可用內存的管理與分配。本文聚焦 Zend MM 核心邏輯,揭秘鮮為人知的數據結構、快取機制與大小塊內存差異化策略。作者:Laruence (惠新宸)。

Zend MM 將內存劃分為 小塊 與 大塊 兩類,差異化處理:

目標:一次定位即完成分配。關鍵結構 free_buckets 為定長數組,存儲 ZEND_MM_NUMBER_BUCKET 個 zend_mm_free_block。
未使用的 bucket 僅需 next_free_block、prev_free_block 兩指針。為節省內存,PHP 未分配完整 zend_mm_free_block 結構,而是僅分配雙指針空間:
#define ZEND_MM_SMALL_FREE_BUCKET(heap, index) \
(zend_mm_free_block*)((char*)&heap->free_buckets[index * 2] + \
sizeof(zend_mm_free_block*) * 2 - \
sizeof(zend_mm_small_free_block))
運行時保證僅訪問 prev/next 兩指針,避免越界讀寫。

#define ZEND_MM_LARGE_BUCKET_INDEX(S) zend_mm_high_bit(S)
zend_mm_high_bit 獲取 size 二進制中最高位 1 的位序(對應匯編指令 bsr)。注意:TIPI 專案曾誤解釋為「1 的個數 - 1」。
true_size 對應 indexlarge_free_bitmap 快速判斷是否存在可用塊free_buckets[index] 有可用塊,從雙向鏈表查找true_size 二進制位從高到低遍歷鍵樹:child[1]child[0],無則進 child[1] 找最小可用塊best_fit = p = heap->large_free_buckets[index + zend_mm_low_bit(bitmap)];
while ((p = p->child[p->child[0] != NULL])) {
if (ZEND_MM_FREE_BLOCK_SIZE(p) < ZEND_MM_FREE_BLOCK_SIZE(best_fit))
best_fit = p;
}
此邏輯確保在大塊區域快速定位最合適內存,兼顧速度與碎片控制。
雙向鏈表結構,暫存分配後剩餘的小塊內存,避免頻繁插入 free_buckets 造成性能抖動。TIPI 專案曾誤描述為「只有兩個元素的數組」。
理解 Zend MM 內存分配策略,對開發高性能擴展、排查內存洩漏、優化長運行腳本至關重要。英文版文檔 Zend MM 亦在持續完善中。