經過測試,將記憶體密集型程式碼分成一個單獨的函式,可以讓垃圾收集正常運作。
例如,原始程式碼如下:-
while(true){
//執行記憶體密集型程式碼
}
可以轉換成如下所示的程式碼:-
function intensive($parameters){
//執行記憶體密集型程式碼
}
while(true){
intensive($parameters);
}
傳統上,參考計數記憶體機制(例如先前由 PHP 使用的機制)無法解決循環參考記憶體洩漏問題;但是,從 5.3.0 版本開始,PHP 實作了» 參考計數系統中的並行循環收集論文中的同步演算法,以解決該問題。
完整說明該演算法如何運作會稍微超出本節範圍,但此處說明基本概念。首先,我們必須建立一些基本規則。如果 refcount 增加,則表示它仍在使用中,因此不是垃圾。如果 refcount 減少並達到零,則可以釋放 zval。這表示僅當 refcount 引數減少為非零值時,才會建立垃圾循環。其次,在垃圾循環中,可以透過檢查是否可以將其 refcount 減少 1,然後檢查哪些 zval 的 refcount 為零,來發現哪些部分是垃圾。
為了避免在每次 refcount 減少時都必須呼叫檢查垃圾循環的動作,演算法會將所有可能的根 (zval) 放入「根緩衝區」中(將它們標記為「紫色」)。它還確保每個可能的垃圾根只會進入緩衝區一次。只有當根緩衝區已滿時,才會開始收集裡面所有不同 zval。請參閱上圖中的步驟 A。
在步驟 B 中,演算法對所有可能的根執行深度優先搜尋,將其找到的每個 zval 的 refcount 減 1,並確保不會將同一個 zval 的 refcount 減少兩次(方法是將它們標記為「灰色」)。在步驟 C 中,演算法再次從每個根節點執行深度優先搜尋,以再次檢查每個 zval 的 refcount。如果它發現 refcount 為零,則將 zval 標記為「白色」(圖中為藍色)。如果它大於零,則從該點開始以深度優先搜尋的方式反轉 refcount 減 1 的動作,並將它們再次標記為「黑色」。在最後一個步驟 (D) 中,演算法會走訪根緩衝區,從那裡移除 zval 根,同時檢查哪些 zval 在上一個步驟中已標記為「白色」。每個標記為「白色」的 zval 都會被釋放。
現在您已經對演算法的工作原理有了基本的了解,我們將回顧一下它如何與 PHP 整合。預設情況下,PHP 的垃圾收集器是開啟的。但是,有一個 php.ini 設定允許您變更此設定:zend.enable_gc。
當垃圾收集器開啟時,只要根緩衝區已滿,就會執行上述的循環尋找演算法。根緩衝區的大小固定為 10,000 個可能的根(雖然您可以透過變更 PHP 原始碼中 Zend/zend_gc.c
內的 GC_THRESHOLD_DEFAULT
常數並重新編譯 PHP 來變更此設定)。當垃圾收集器關閉時,永遠不會執行循環尋找演算法。但是,無論是否已使用此配置設定啟動垃圾收集機制,可能的根都會始終記錄在根緩衝區中。
如果在垃圾收集機制關閉的情況下,根緩衝區已滿,且填滿可能的根,則進一步的可能根將不會被記錄。那些未記錄的可能根永遠不會被演算法分析。如果它們是循環參考循環的一部分,則永遠不會被清理,並會產生記憶體洩漏。
即使機制已停用,仍會記錄可能的根,原因是因為記錄可能的根比每次找到可能的根時都必須檢查機制是否開啟來得快。但是,垃圾收集和分析機制本身可能會花費大量時間。
除了變更 zend.enable_gc 配置設定外,也可以分別透過呼叫 gc_enable() 或 gc_disable() 來開啟和關閉垃圾收集機制。呼叫這些函式具有與使用配置設定開啟或關閉機制的相同效果。即使可能的根緩衝區尚未填滿,也可以強制收集循環。為此,您可以使用 gc_collect_cycles() 函式。此函式會傳回演算法收集的循環數。
能夠開啟和關閉機制以及自行啟動循環收集的原理是,應用程式的某些部分可能對時間非常敏感。在這些情況下,您可能不希望垃圾收集機制啟動。當然,透過關閉應用程式某些部分的垃圾收集,您確實冒著產生記憶體洩漏的風險,因為某些可能的根可能不適合放入有限的根緩衝區中。因此,在您呼叫 gc_disable() 之前呼叫 gc_collect_cycles() 可能是明智之舉,以釋放可能因已記錄在根緩衝區中的可能根而遺失的記憶體。然後,這會留下一個空的緩衝區,以便在循環收集機制關閉時,有更多空間可以儲存可能的根。
經過測試,將記憶體密集型程式碼分成一個單獨的函式,可以讓垃圾收集正常運作。
例如,原始程式碼如下:-
while(true){
//執行記憶體密集型程式碼
}
可以轉換成如下所示的程式碼:-
function intensive($parameters){
//執行記憶體密集型程式碼
}
while(true){
intensive($parameters);
}
── 未使用的物件 ─── ─ 使用中的物件
↓ ↓ ↓
_____________________________________
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
|□□□□□□□□□□□□□□□□□|██■■■■■■■■■■■■■■■■|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
▲ ▲
未參考的 已參考的
物件 物件
█ 記憶體洩漏