內存是計算機非常關鍵的部件之一,是暫時存儲程序以及數據的空間,CPU只有有限的寄存器可以用于 存儲計算數據,而大部分的數據都是存儲在內存中的,程序運行都是在內存中進行的。和CPU計算能力一樣, 內存也是決定計算效率的一個關鍵部分。
計算中的資源中主要包含:CPU計算能力,內存資源以及I/O。現代計算機為了充分利用資源, 而出現了多任務操作系統,通過進程調度來共享CPU計算資源,通過虛擬存儲來分享內存存儲能力。 本章的內存管理中不會介紹操作系統級別的虛擬存儲技術,而是關注在應用層面: 如何高效的利用有限的內存資源。
目前除了使用C/C++等這類的低層編程語言以外,很多編程語言都將內存管理移到了語言之后, 例如Java, 各種腳本語言:PHP/Python/Ruby等等,程序手動維護內存的成本非常大, 而這些腳本語言或新型語言都專注于特定領域,這樣能將程序員從內存管理中解放出來專注于業務的實現。 雖然程序員不需要手動維護內存,而在程序運行過程中內存的使用還是要進行管理的, 內存管理的工作也就編程語言實現程序員的工作了。
內存管理的主要工作是盡可能高效的利用內存。
內存的使用操作包括申請內存,銷毀內存,修改內存的大小等。 如果申請了內存在使用完后沒有及時釋放則可能會造成內存泄露,如果這種情況出現在常駐程序中, 久而久之,程序會把機器的內存耗光。所以對于類似于PHP這樣沒有低層內存管理的語言來說, 內存管理是其至關重要的一個模塊,它在很大程度上決定了程序的執行效率。
在PHP層面來看,定義的變量、類、函數等實體在運行過程中都會涉及到內存的申請和釋放, 例如變量可能會在超出作用域后會進行銷毀,在計算過程中會產生的臨時數據等都會有內存操作, 像類對象,函數定義等數據則會在請求結束之后才會被釋放。在這過程中合適申請內存合適釋放內存就比較關鍵了。 PHP從開始就有一套屬于自己的內存管理機制,在5.3之前使用的是經典的引用計數技術, 但引用計數存在一定的技術缺陷,在PHP5.3之后,引入了新的垃圾回收機制,至此,PHP的內存管理機制更加完善。
本章將介紹PHP語言實現中的內存管理技術實現。
應用層的內存管理
由于計算機的內存由操作系統進行管理,所以普通應用程序是無法直接對內存進行訪問的, 應用程序只能向操作系統申請內存,通常的應用也是這么做的,在需要的時候通過類似malloc之類的庫函數 向操作系統申請內存,在一些對性能要求較高的應用場景下是需要頻繁的使用和釋放內存的, 比如Web服務器,編程語言等,由于向操作系統申請內存空間會引發系統調用, 系統調用和普通的應用層函數調用性能差別非常大,因為系統調用會將CPU從用戶態切換到內核, 因為涉及到物理內存的操作,只有操作系統才能進行,而這種切換的成本是非常大的, 如果頻繁的在內核態和用戶態之間切換會產生性能問題。
鑒于系統調用的開銷,一些對性能有要求的應用通常會自己在用戶態進行內存管理, 例如第一次申請稍大的內存留著備用,而使用完釋放的內存并不是馬上歸還給操作系統, 可以將內存進行復用,這樣可以避免多次的內存申請和釋放所帶來的性能消耗。
PHP不需要顯式的對內存進行管理,這些工作都由Zend引擎進行管理了。PHP內部有一個內存管理體系, 它會自動將不再使用的內存垃圾進行釋放,這部分的內容后面的小節會介紹到。
PHP中內存相關的功能特性
可能有很多的讀者碰到過類似下面的錯誤吧:
Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes)
這個錯誤的信息很明確,PHP已經達到了允許使用的最大內存了,通常上來說這很有可能是我們的程序編寫的有些問題。 比如:一次性讀取超大的文件到內存中,或者出現超大的數組,或者在大循環中的沒有及時釋放掉不再使用的變量, 這些都有可能會造成內存占用過大而被終止。
PHP默認的最大內存使用大小是32M, 如果你真的需要使用超過32M的內存可以修改php.ini配置文件的如下配置:
memory\_limit = 32M
如果你無法修改php配置文件,如果你的PHP環境沒有禁用ini\_set()函數,也可以動態的修改最大的內存占用大小:
ini\_set("memory\_limit", "128M");
既然我們能動態的調整最大的內存占用,那我們是否有辦法獲取目前的內存占用情況呢?答案是肯定的。
**memory\_get\_usage(),這個函數的作用是獲取 目前PHP腳本所用的內存大小。**
memory\_get\_peak\_usage(),這個函數的作用返回 當前腳本到目前位置所占用的內存峰值,這樣就可能獲取到目前的腳本的內存需求情況。
單就PHP用戶空間提供的功能來說,我們似乎無法控制內存的使用,只能被動的獲取內存的占用情況, 這樣的話我們學習內存管理有什么用呢?
前面的章節有介紹到引用計數,函數表,符號表,常量表等。當我們明白這些信息都會占用內存的時候, 我們可以有意的避免不必要的浪費內存,比如我們在項目中通常會使用autoload來避免一次性把不一定會使用的類 包含進來,而這些信息是會占用內存的,如果我們及時把不再使用的變量unset掉之后可能會釋放掉它所占用的空間,
內存管理一般會包括以下內容:
1、是否有足夠的內存供我們的程序使用;
2、如何從足夠可用的內存中獲取部分內存;
3、對于使用后的內存,是否可以將其銷毀并將其重新分配給其它程序使用。
與此對應,PHP的內容管理也包含這樣的內容,只是這些內容在ZEND內核中是以宏的形式作為接口提供給外部使用。 后面兩個操作分別對應emalloc宏,efree宏,而第一個操作可以根據emalloc宏返回結果檢測。
PHP的內存管理可以被看作是分層(hierarchical)的。
它分為三層:存儲層(storage)、堆層(heap)和接口層(emalloc/efree)。
存儲層通過 malloc()、mmap() 等函數向系統真正的申請內存,并通過 free() 函數釋放所申請的內存。
存儲層通常申請的內存塊都比較大,這里申請的內存大并不是指storage層結構所需要的內存大,
只是堆層通過調用存儲層的分配方法時,其以大塊大塊的方式申請的內存,存儲層的作用是將內存分配的方式對堆層透明化。
如圖6.1所示,PHP內存管理器。PHP在存儲層共有4種內存分配方案: malloc,win32,mmap\_anon,mmap\_zero, 默認使用malloc分配內存,如果設置了ZEND\_WIN32宏,則為windows版本,調用HeapAlloc分配內存, 剩下兩種內存方案為匿名內存映射,并且PHP的內存方案可以通過設置環境變量來修改。

首先我們看下接口層的實現,接口層是一些宏定義,如下:
#define emalloc(size)? ? ? ? ? ? ? ? ? ? ? \_emalloc((size) ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define safe\_emalloc(nmemb, size, offset)? \_safe\_emalloc((nmemb), (size), (offset) ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define efree(ptr)? ? ? ? ? ? ? ? ? ? ? ? ? \_efree((ptr) ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define ecalloc(nmemb, size)? ? ? ? ? ? ? ? \_ecalloc((nmemb), (size) ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define erealloc(ptr, size)? ? ? ? ? ? ? ? \_erealloc((ptr), (size), 0 ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define safe\_erealloc(ptr, nmemb, size, offset) \_safe\_erealloc((ptr), (nmemb), (size), (offset) ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define erealloc\_recoverable(ptr, size)? ? \_erealloc((ptr), (size), 1 ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define estrdup(s)? ? ? ? ? ? ? ? ? ? ? ? ? \_estrdup((s) ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define estrndup(s, length)? ? ? ? ? ? ? ? \_estrndup((s), (length) ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)#define zend\_mem\_block\_size(ptr)? ? ? ? ? ? \_zend\_mem\_block\_size((ptr) TSRMLS\_CC ZEND\_FILE\_LINE\_CC ZEND\_FILE\_LINE\_EMPTY\_CC)
這里為什么沒有直接調用函數?因為這些宏相當于一個接口層或中間層,定義了一個高層次的接口,使得調用更加容易 它隔離了外部調用和PHP內存管理的內部實現,實現了一種松耦合關系。雖然PHP不限制這些函數的使用, 但是官方文檔還是建議使用這些宏。這里的接口層有點門面模式(facade模式)的味道。
在接口層下面是PHP內存管理的核心實現,我們稱之為heap層。 這個層控制整個PHP內存管理的過程,首先我們看這個層的結構:
/\* mm block type \*/
typedef struct \_zend\_mm\_block\_info {
size\_t \_size;? /\* block的大小\*/
size\_t \_prev;? /\* 計算前一個塊有用到\*/
} zend\_mm\_block\_info;
typedef struct \_zend\_mm\_block {
zend\_mm\_block\_info info;
} zend\_mm\_block;
typedef struct \_zend\_mm\_small\_free\_block {? /\* 雙向鏈表 \*/
zend\_mm\_block\_info info;
struct \_zend\_mm\_free\_block \*prev\_free\_block;? ? /\* 前一個塊 \*/
struct \_zend\_mm\_free\_block \*next\_free\_block;? ? /\* 后一個塊 \*/
} zend\_mm\_small\_free\_block; /\* 小的空閑塊\*/
typedef struct \_zend\_mm\_free\_block {? ? /\* 雙向鏈表 + 樹結構 \*/
zend\_mm\_block\_info info;
struct \_zend\_mm\_free\_block \*prev\_free\_block;? ? /\* 前一個塊 \*/
struct \_zend\_mm\_free\_block \*next\_free\_block;? ? /\* 后一個塊 \*/
struct \_zend\_mm\_free\_block \*\*parent;? ? /\* 父結點 \*/
struct \_zend\_mm\_free\_block \*child\[2\];? /\* 兩個子結點\*/
} zend\_mm\_free\_block;
struct \_zend\_mm\_heap {
int? ? ? ? ? ? ? ? use\_zend\_alloc; /\* 是否使用zend內存管理器 \*/
void? ? ? ? ? ? ? \*(\*\_malloc)(size\_t); /\* 內存分配函數\*/
void? ? ? ? ? ? ? ? (\*\_free)(void\*);? ? /\* 內存釋放函數\*/
void? ? ? ? ? ? ? \*(\*\_realloc)(void\*, size\_t);
size\_t? ? ? ? ? ? ? free\_bitmap;? ? /\* 小塊空閑內存標識 \*/
size\_t? ? ? ? ? ? ? large\_free\_bitmap;? /\* 大塊空閑內存標識\*/
size\_t? ? ? ? ? ? ? block\_size;? ? /\* 一次內存分配的段大小,即ZEND\_MM\_SEG\_SIZE指定的大小,默認為ZEND\_MM\_SEG\_SIZE? (256 \* 1024)\*/
size\_t? ? ? ? ? ? ? compact\_size;? /\* 壓縮操作邊界值,為ZEND\_MM\_COMPACT指定大小,默認為 2 \* 1024 \* 1024\*/
zend\_mm\_segment? ? \*segments\_list;? /\* 段指針列表 \*/
zend\_mm\_storage? ? \*storage;? ? /\* 所調用的存儲層 \*/
size\_t? ? ? ? ? ? ? real\_size;? /\* 堆的真實大小 \*/
size\_t? ? ? ? ? ? ? real\_peak;? /\* 堆真實大小的峰值 \*/
size\_t? ? ? ? ? ? ? limit;? /\* 堆的內存邊界 \*/
size\_t? ? ? ? ? ? ? size;? /\* 堆大小 \*/
size\_t? ? ? ? ? ? ? peak;? /\* 堆大小的峰值\*/
size\_t? ? ? ? ? ? ? reserve\_size;? /\* 備用堆大小\*/
void? ? ? ? ? ? ? \*reserve;? ? /\* 備用堆 \*/
int? ? ? ? ? ? ? ? overflow;? /\* 內存溢出數\*/
int? ? ? ? ? ? ? ? internal;
#if ZEND\_MM\_CACHE
unsigned int? ? ? ? cached; /\* 已緩存大小 \*/
zend\_mm\_free\_block \*cache\[ZEND\_MM\_NUM\_BUCKETS\]; /\* 緩存數組/
#endif
zend\_mm\_free\_block \*free\_buckets\[ZEND\_MM\_NUM\_BUCKETS\*2\];? ? /\* 小塊內存數組,相當索引的角色 \*/
zend\_mm\_free\_block \*large\_free\_buckets\[ZEND\_MM\_NUM\_BUCKETS\];? ? /\* 大塊內存數組,相當索引的角色 \*/
zend\_mm\_free\_block \*rest\_buckets\[2\];? ? /\* 剩余內存數組\*/
};
當初始化內存管理時,調用函數是zend\_mm\_startup。它會初始化storage層的分配方案, 初始化段大小,壓縮邊界值,并調用zend\_mm\_startup\_ex()初始化堆層。 這里的分配方案就是圖6.1所示的四種方案,它對應的環境變量名為:ZEND\_MM\_MEM\_TYPE。 這里的初始化的段大小可以通過ZEND\_MM\_SEG\_SIZE設置,如果沒設置這個環境變量,程序中默認為256 \* 1024。 這個值存儲在\_zend\_mm\_heap結構的block\_size字段中,將來在維護的三個列表中都沒有可用的內存中,會參考這個值的大小來申請內存的大小。
PHP中的內存管理主要工作就是維護三個列表:**小塊內存列表(free\_buckets)**、**大塊內存列表(large\_free\_buckets)**和**剩余內存列表(rest\_buckets)**。 看到bucket這個單詞是不是很熟悉?在前面我們介紹HashTable時,這就是一個重要的角色,它作為HashTable中的一個單元角色。 在這里,每個bucket也對應一定大小的內存塊列表,這樣的列表都包含雙向鏈表的實現。
我們可以把維護的前面兩個表看作是兩個HashTable,那么,每個HashTable都會有自己的hash函數。 首先我們來看free\_buckets列表,這個列表用來存儲小塊的內存分配,其hash函數為:
#define ZEND\_MM\_BUCKET\_INDEX(true\_size)? ? ((true\_size>>ZEND\_MM\_ALIGNMENT\_LOG2)-(ZEND\_MM\_ALIGNED\_MIN\_HEADER\_SIZE>>ZEND\_MM\_ALIGNMENT\_LOG2))
假設ZEND\_MM\_ALIGNMENT為8(如果沒有特殊說明,本章的ZEND\_MM\_ALIGNMENT的值都為8),則ZEND\_MM\_ALIGNED\_MIN\_HEADER\_SIZE=16, 若此時true\_size=256,則((256>>3)-(16>>3))= 30。 當ZEND\_MM\_BUCKET\_INDEX宏出現時,ZEND\_MM\_SMALL\_SIZE宏一般也會同時出現, ZEND\_MM\_SMALL\_SIZE宏的作用是判斷所申請的內存大小是否為小塊的內存, 在上面的示例中,小于272Byte的內存為小塊內存,則index最多只能為31, 這樣就保證了free\_buckets不會出現數組溢出的情況。
在內存管理初始化時,PHP內核對初始化free\_buckets列表。 從heap的定義我們可知free\_buckets是一個數組指針,其存儲的本質是指向zend\_mm\_free\_block結構體的指針。 開始時這些指針都沒有指向具體的元素,只是一個簡單的指針空間。 free\_buckets列表在實際使用過程中只存儲指針,這些指針以兩個為一對(即數組從0開始,兩個為一對),分別存儲一個個雙向鏈表的頭尾指針。 其結構如圖6.2所示。

對于free\_buckets列表位置的獲取,關鍵在于ZEND\_MM\_SMALL\_FREE\_BUCKET宏,宏代碼如下:
#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))
仔細看這個宏實現,發現在它的計算過程是取free\_buckets列表的偶數位的內存地址加上 兩個指針的內存大小并減去zend\_mm\_small\_free\_block結構所占空間的大小。 而zend\_mm\_free\_block結構和zend\_mm\_small\_free\_block結構的差距在于兩個指針。 據此計算過程可知,ZEND\_MM\_SMALL\_FREE\_BUCKET宏會獲取free\_buckets列表 index對應雙向鏈表的第一個zend\_mm\_free\_block的prev\_free\_block指向的位置。 free\_buckets的計算僅僅與prev\_free\_block指針和next\_free\_block指針相關, 所以free\_buckets列表也僅僅需要存儲這兩個指針。
那么,這個數組在最開始是怎樣的呢? 在初始化函數zend\_mm\_init中free\_buckets與large\_free\_buckts列表一起被初始化。 如下代碼:
p=ZEND\_MM\_SMALL\_FREE\_BUCKET(heap,0);for(i=0;inext\_free\_block=p;p->prev\_free\_block=p;p=(zend\_mm\_free\_block\*)((char\*)p+sizeof(zend\_mm\_free\_block\*)\*2);heap->large\_free\_buckets\[i\]=NULL;}
對于free\_buckets列表來說,在循環中,偶數位的元素(索引從0開始)將其next\_free\_block和prev\_free\_block都指向自己, 以i=0為例,free\_buckets的第一個元素(free\_buckets\[0\])存儲的是第二個元素(free\_buckets\[1\])的地址, 第二個元素存儲的是第一個元素的地址。 此時將可能會想一個問題,在整個free\_buckets列表沒有內容時,ZEND\_MM\_SMALL\_FREE\_BUCKET在獲取第一個zend\_mm\_free\_block時, 此zend\_mm\_free\_block的next\_free\_block元素和prev\_free\_block元素卻分別指向free\_buckets\[0\]和free\_buckets\[1\]。
在整個循環初始化過程中都沒有free\_buckets數組的下標操作,它的移動是通過地址操作,以加兩個sizeof(zend\_mm\_free\_block\*)實現, 這里的sizeof(zend\_mm\_free\_block\*)是獲取指針的大小。比如現在是在下標為0的元素的位置, 加上兩個指針的值后,指針會指向下標為2的地址空間,從而實現數組元素的向后移動, 也就是zend\_mm\_free\_block->next\_free\_block和zend\_mm\_free\_block->prev\_free\_block位置的后移。 這種不存儲zend\_mm\_free\_block數組,僅存儲其指針的方式不可不說精妙。雖然在理解上有一些困難,但是節省了內存。
free\_buckets列表使用free\_bitmap標記是否該雙向鏈表已經使用過時有用。 當有新的元素需要插入到列表時,需要先根據塊的大小查找index, 查找到index后,在此index對應的雙向鏈表的頭部插入新的元素。
free\_buckets列表的作用是存儲小塊內存,而與之對應的large\_free\_buckets列表的作用是存儲大塊的內存, 雖然large\_free\_buckets列表也類似于一個hash表,但是這個與前面的free\_buckets列表一些區別。 它是一個集成了數組,樹型結構和雙向鏈表三種數據結構的混合體。 我們先看其數組結構,數組是一個hash映射,其hash函數為:
#define ZEND\_MM\_LARGE\_BUCKET\_INDEX(S) zend\_mm\_high\_bit(S)staticinlineunsignedintzend\_mm\_high\_bit(size\_t \_size){..//省略若干不同環境的實現unsignedintn=0;while(\_size!=0){\_size=\_size>>1;n++;}returnn-1;}
這個hash函數用來計算size中最高位的1的比特位是多少,這點從其函數名就可以看出。 假設此時size為512Byte,則這段內存會放在large\_free\_buckets列表, 512的二進制碼為1000000000,則zend\_mm\_high\_bit(512)計算的值為9,則其對應的列表index為9。 關于右移操作,這里有一點說明:
一般來說,右移分為邏輯右移和算術右移。邏輯位移在在左端補K個0,算術右移在左端補K個最高有效位的值。 C語言標準沒有明確定義應該使用哪種方式。對于無符號數據,右移必須是邏輯的。對于有符號的數據,則二者都可以。 但是,現實中都會默認為算術右移。
以上的zend\_mm\_high\_bit函數實現是節選的最后C語言部分(如果對匯編不了解的話,看這部分會比較容易一些)。 但是它卻是最后一種選擇,在其它環境中,如x86的處理中可以使用匯編語言BSR達到這段代碼的目的,這樣的速度會更快一些。 這個匯編語句是BSR(Bit Scan Reverse),BSR被稱為逆向位掃描指令。 它使用方法為: BSF dest,src,它的作用是從源操作數的的最高位向低位搜索,將遇到的第一個“1”所在的位序號存入目標寄存器。
我們通過一次列表的元素插入操作來理解列表的結果。 首先確定當前需要內存所在的數組元素位置,然后查找此內存大小所在的位置。 這個查找行為是發生在樹型結構中,而樹型結構的位置與內存的大小有關。 其查找過程如下:
第一步 通過索引獲取樹型結構第一個結點并作為當前結點,如果第一個結點為空,則將內存放到第一個元素的結點位置,返回,否則轉第二步
第二步 從當前結點出發,查找下一個結點,并將其作為當前結點
第三步 判斷當前結點內存的大小與需要分配的內存大小是否一樣 如果大小一樣則以雙向鏈表的結構將新的元素添加到結點元素的后面第一個元素的位置。否則轉四步
第四步 判斷當前結點是否為空,如果為空,則占據結點位置,結束查找,否則第二步。
從以上的過程我們可以畫出large\_free\_buckets列表的結構如圖6.3所示:

從內存分配的過程中可以看出,內存塊查找判斷順序依次是小塊內存列表,大塊內存列表,剩余內存列表。 在heap結構中,剩余內存列表對應rest\_buckets字段,這是一個包含兩個元素的數組, 并且也是一個雙向鏈表隊列,其中rest\_buckets\[0\]為隊列的頭,rest\_buckets\[1\]為隊列的尾。 而我們常用的插入和查找操作是針對第一個元素,即heap->rest\_buckets\[0\], 當然,這是一個雙向鏈表隊列,隊列的頭和尾并沒有很明顯的區別。它們僅僅是作為一種認知上的區分。 在添加內存時,如果所需要的內存塊的大小大于初始化時設置的ZEND\_MM\_SEG\_SIZE的值(在heap結構中為block\_size字段) 與ZEND\_MM\_ALIGNED\_SEGMENT\_SIZE(等于8)和ZEND\_MM\_ALIGNED\_HEADER\_SIZE(等于8)的和的差,則會將新生成的塊插入 rest\_buckts所在的雙向鏈表中,這個操作和前面的雙向鏈表操作一樣,都是從”隊列頭“插入新的元素。 此列表的結構和free\_bucket類似,只是這個列表所在的數組沒有那么多元素,也沒有相應的hash函數。
在heap層下面是存儲層,存儲層的作用是將內存分配的方式對堆層透明化,實現存儲層和heap層的分離。 在PHP的源碼中有注釋顯示相關代碼為"Storage Manager"。 存儲層的主要結構代碼如下:
/\* Heaps with user defined storage \*/typedefstruct\_zend\_mm\_storage zend\_mm\_storage;typedefstruct\_zend\_mm\_segment{size\_t? ? size;struct\_zend\_mm\_segment\*next\_segment;}zend\_mm\_segment;typedefstruct\_zend\_mm\_mem\_handlers{constchar\*name;zend\_mm\_storage\*(\*init)(void\*params);//? ? 初始化函數void(\*dtor)(zend\_mm\_storage\*storage);//? ? 析構函數void(\*compact)(zend\_mm\_storage\*storage);zend\_mm\_segment\*(\*\_alloc)(zend\_mm\_storage\*storage,size\_t size);//? ? 內存分配函數zend\_mm\_segment\*(\*\_realloc)(zend\_mm\_storage\*storage,zend\_mm\_segment\*ptr,size\_t size);//? ? 重新分配內存函數void(\*\_free)(zend\_mm\_storage\*storage,zend\_mm\_segment\*ptr);//? ? 釋放內存函數}zend\_mm\_mem\_handlers;struct\_zend\_mm\_storage{constzend\_mm\_mem\_handlers\*handlers;//? ? 處理函數集void\*data;};
以上代碼的關鍵在于存儲層處理函數的結構體,對于不同的內存分配方案,所不同的就是內存分配的處理函數。 其中以name字段標識不同的分配方案。在圖6.1中,我們可以看到PHP在存儲層共有4種內存分配方案: malloc,win32,mmap\_anon,mmap\_zero默認使用malloc分配內存, 如果設置了ZEND\_WIN32宏,則為windows版本,調用HeapAlloc分配內存,剩下兩種內存方案為匿名內存映射, 并且PHP的內存方案可以通過設置變量來修改。其官方說明如下:
The Zend MM can be tweaked using ZEND\_MM\_MEM\_TYPE and ZEND\_MM\_SEG\_SIZE environment
variables. Default values are “malloc” and “256K”. Dependent on target system you
can also use “mmap\_anon”, “mmap\_zero” and “win32″ storage managers.
在代碼中,對于這4種內存分配方案,分別對應實現了zend\_mm\_mem\_handlers中的各個處理函數。 配合代碼的簡單說明如下:
/\* 使用mmap內存映射函數分配內存 寫入時拷貝的私有映射,并且匿名映射,映射區不與任何文件關聯。\*/# define ZEND\_MM\_MEM\_MMAP\_ANON\_DSC {"mmap\_anon", zend\_mm\_mem\_dummy\_init, zend\_mm\_mem\_dummy\_dtor, zend\_mm\_mem\_dummy\_compact, zend\_mm\_mem\_mmap\_anon\_alloc, zend\_mm\_mem\_mmap\_realloc, zend\_mm\_mem\_mmap\_free}/\* 使用mmap內存映射函數分配內存 寫入時拷貝的私有映射,并且映射到/dev/zero。\*/# define ZEND\_MM\_MEM\_MMAP\_ZERO\_DSC {"mmap\_zero", zend\_mm\_mem\_mmap\_zero\_init, zend\_mm\_mem\_mmap\_zero\_dtor, zend\_mm\_mem\_dummy\_compact, zend\_mm\_mem\_mmap\_zero\_alloc, zend\_mm\_mem\_mmap\_realloc, zend\_mm\_mem\_mmap\_free}/\* 使用HeapAlloc分配內存 windows版本 關于這點,注釋中寫的是VirtualAlloc() to allocate memory,實際在程序中使用的是HeapAlloc\*/# define ZEND\_MM\_MEM\_WIN32\_DSC {"win32", zend\_mm\_mem\_win32\_init, zend\_mm\_mem\_win32\_dtor, zend\_mm\_mem\_win32\_compact, zend\_mm\_mem\_win32\_alloc, zend\_mm\_mem\_win32\_realloc, zend\_mm\_mem\_win32\_free}/\* 使用malloc分配內存 默認為此種分配 如果有加ZEND\_WIN32宏,則使用win32的分配方案\*/# define ZEND\_MM\_MEM\_MALLOC\_DSC {"malloc", zend\_mm\_mem\_dummy\_init, zend\_mm\_mem\_dummy\_dtor, zend\_mm\_mem\_dummy\_compact, zend\_mm\_mem\_malloc\_alloc, zend\_mm\_mem\_malloc\_realloc, zend\_mm\_mem\_malloc\_free}staticconstzend\_mm\_mem\_handlers mem\_handlers\[\]={#ifdef HAVE\_MEM\_WIN32ZEND\_MM\_MEM\_WIN32\_DSC,#endif#ifdef HAVE\_MEM\_MALLOCZEND\_MM\_MEM\_MALLOC\_DSC,#endif#ifdef HAVE\_MEM\_MMAP\_ANONZEND\_MM\_MEM\_MMAP\_ANON\_DSC,#endif#ifdef HAVE\_MEM\_MMAP\_ZEROZEND\_MM\_MEM\_MMAP\_ZERO\_DSC,#endif{NULL,NULL,NULL,NULL,NULL,NULL}};
假設我們使用的是win32內存方案,則在PHP編譯時,編譯器會選擇將ZEND\_MM\_MEM\_WIN32\_DSC宏所代碼的所有處理函數賦值給mem\_handlers。 在之后我們調用內存分配時,將會使用此數組中對應的相關函數。當然,在指定環境變量 USE\_ZEND\_ALLOC 時,可用于允許在運行時選擇 malloc 或 emalloc 內存分配。 使用 malloc-type 內存分配將允許外部調試器觀察內存使用情況,而 emalloc 分配將使用 Zend 內存管理器抽象,要求進行內部調試
- 技能知識點
- 對死鎖問題的理解
- 文件系統原理:如何用1分鐘遍歷一個100TB的文件?
- 數據庫原理:為什么PrepareStatement性能更好更安全?
- Java Web程序的運行時環境到底是怎樣的?
- 你真的知道自己要解決的問題是什么嗎?
- 如何解決問題
- 經驗分享
- GIT的HTTP方式免密pull、push
- 使用xhprof對php7程序進行性能分析
- 微信掃碼登錄和使用公眾號方式進行掃碼登錄
- 關于curl跳轉抓取
- Linux 下配置 Git 操作免登錄 ssh 公鑰
- Linux Memcached 安裝
- php7安裝3.4版本的phalcon擴展
- centos7下php7.0.x安裝phalcon框架
- 將字符串按照指定長度分割
- 搜索html源碼中標簽包的純文本
- 更換composer鏡像源為阿里云
- mac 隱藏文件顯示/隱藏
- 谷歌(google)世界各國網址大全
- 實戰文檔
- PHP7安裝intl擴展和linux安裝icu
- linux編譯安裝時常見錯誤解決辦法
- linux刪除文件后不釋放磁盤空間解決方法
- PHP開啟異步多線程執行腳本
- file_exists(): open_basedir restriction in effect. File完美解決方案
- PHP 7.1 安裝 ssh2 擴展,用于PHP進行ssh連接
- php命令行加載的php.ini
- linux文件實時同步
- linux下php的psr.so擴展源碼安裝
- php將字符串中的\n變成真正的換行符?
- PHP7 下安裝 memcache 和 memcached 擴展
- PHP 高級面試題 - 如果沒有 mb 系列函數,如何切割多字節字符串
- PHP設置腳本最大執行時間的三種方法
- 升級Php 7.4帶來的兩個大坑
- 不同域名的iframe下,fckeditor在chrome下的SecurityError,解決辦法~~
- Linux find+rm -rf 執行組合刪除
- 從零搭建Prometheus監控報警系統
- Bug之group_concat默認長度限制
- PHP生成的XML顯示無效的Char值27消息(PHP generated XML shows invalid Char value 27 message)
- XML 解析中,如何排除控制字符
- PHP各種時間獲取
- nginx配置移動自適應跳轉
- 已安裝nginx動態添加模塊
- auto_prepend_file與auto_append_file使用方法
- 利用nginx實現web頁面插入統計代碼
- Nginx中的rewrite指令(break,last,redirect,permanent)
- nginx 中 index try_files location 這三個配置項的作用
- linux安裝git服務器
- PHP 中運用 elasticsearch
- PHP解析Mysql Binlog
- 好用的PHP學習網(持續更新中)
- 一篇寫給準備升級PHP7的小伙伴的文章
- linux 安裝php7 -系統centos7
- Linux 下多php 版本共存安裝
- PHP編譯安裝時常見錯誤解決辦法,php編譯常見錯誤
- nginx upstream模塊--負載均衡
- 如何解決Tomcat服務器打開不了HOST Manager的問題
- PHP的內存泄露問題與垃圾回收
- Redis數據結構 - string字符串
- PHP開發api接口安全驗證
- 服務接口API限流 Rate Limit
- php內核分析---內存管理(一)
- PHP內存泄漏問題解析
- 【代碼片-1】 MongoDB與PHP -- 高級查詢
- 【代碼片-1】 php7 mongoDB 簡單封裝
- php與mysql系統中出現大量數據庫sleep的空連接問題分析
- 解決crond引發大量sendmail、postdrop進程問題
- PHP操作MongoDB GridFS 存儲文件,如圖片文件
- 淺談php安全
- linux上keepalived+nginx實現高可用web負載均衡
- 整理php防注入和XSS攻擊通用過濾