掃二維碼與項(xiàng)目經(jīng)理溝通
我們在微信上24小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
本文將要講述 PHP 發(fā)展歷程中的垃圾回收及內(nèi)存管理相關(guān)內(nèi)容。

創(chuàng)新互聯(lián)建站長期為上千多家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為屏邊企業(yè)提供專業(yè)的網(wǎng)站制作、成都網(wǎng)站制作,屏邊網(wǎng)站改版等技術(shù)服務(wù)。擁有10多年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開發(fā)。
在 PHP 5.2 及以前的版本中,PHP 的垃圾回收采用的是 引用計(jì)數(shù) 算法。
引用計(jì)數(shù)基礎(chǔ)知識
php 的變量存儲在「zval」變量容器(數(shù)據(jù)結(jié)構(gòu))中,「zval」屬性包含如下信息:
當(dāng)一個(gè)變量被賦值時(shí),就會(huì)生成一個(gè)對應(yīng)的「zavl」變量容器。【推薦學(xué)習(xí):PHP視頻教程】
要查看變量的「zval」容器信息(即查看變量的 is_ref 和 refcount),可以使用 XDebug 調(diào)試工具的 xdebug_debug_zval() 函數(shù)。
安裝 XDebug 擴(kuò)展插件的方法可以查看 這個(gè)教程(https://github.com/huliuqing/phpnotes/issues/58),有關(guān)XDebug 使用方法請閱讀 官方文檔(https://xdebug.org/docs/)。
假設(shè),我們已經(jīng)成功安裝好 XDebug 工具,現(xiàn)在就可以來對變量進(jìn)行調(diào)試了。
如果我們的 PHP 語句只是對變量進(jìn)行簡單賦值時(shí),is_ref 標(biāo)識值為 0,refcount 值為 1;若將這個(gè)變量作為值賦值給另一個(gè)變量時(shí),則增加 zval 變量容器的 refcount 計(jì)數(shù);同理,銷毀(unset)變量時(shí),「refcount」相應(yīng)的減去 1。
請看下面的示例:
通過前面的簡單變量的 zval 信息我們知道 $copy 和 $name 共用 zval 變量容器(內(nèi)存),然后通過 refcount 來表示當(dāng)前這個(gè) zval 被多少個(gè)變量使用。
看個(gè)實(shí)例:
注意到?jīng)]有,當(dāng)將值 liugongzi handsome 賦值給變量 $copy 時(shí),name 和 copy 的 refcount 值都變成了 1,在這個(gè)過程中發(fā)生以下幾個(gè)操作:
這里只是簡單對「寫時(shí)復(fù)制」進(jìn)行介紹,感興趣的朋友可以閱讀文末給出的參考資料進(jìn)行更加深入的研究。
引用傳值(&)的「引用計(jì)數(shù)」規(guī)則同普通賦值語句一樣,只是 is_ref 標(biāo)識的值為 1 表示該變量是引用傳值類型。
我們現(xiàn)在來看看引用傳值的示例:
與標(biāo)量類型(整型、浮點(diǎn)型、布爾型等)不同,數(shù)組(array)和對象(object)這種符合類型的引用計(jì)數(shù)規(guī)則會(huì)稍復(fù)雜一些。
為了更好的說明,還是先看看數(shù)組的引用計(jì)數(shù)示例:
$a = array( 'meaning' => 'life', 'number' => 42 ); xdebug_debug_zval( 'a' ); // a: // (refcount=1, is_ref=0) // array (size=2) // 'meaning' => (refcount=1, is_ref=0)string 'life' (length=4) // 'number' => (refcount=1, is_ref=0)int 42
上面的引用計(jì)數(shù)示意圖如下:
從圖中我們發(fā)現(xiàn)復(fù)合類型的引用計(jì)數(shù)規(guī)則基本上同標(biāo)量的計(jì)數(shù)規(guī)則一樣,就給出的示例來說,PHP 會(huì)創(chuàng)建 3 個(gè) zval 變量容器,一個(gè)用于存儲數(shù)組本身,另外兩個(gè)用于存儲數(shù)組中的元素。
添加一個(gè)已經(jīng)存在的元素到數(shù)組中時(shí),它的引用計(jì)數(shù)器 refcount 會(huì)增加 1。
$a = array( 'meaning' => 'life', 'number' => 42 ); xdebug_debug_zval( 'a' ); $a['life'] = $a['meaning']; xdebug_debug_zval( 'a' ); // a: // (refcount=1, is_ref=0) // array (size=3) // 'meaning' => (refcount=2, is_ref=0)string 'life' (length=4) // 'number' => (refcount=0, is_ref=0)int 42 // 'life' => (refcount=2, is_ref=0)string 'life' (length=4)
大致示意圖如下:
雖然,復(fù)合類型的引用計(jì)數(shù)規(guī)則同標(biāo)量類型大致相同,但是如果引用的值為變量自身(即循環(huán)應(yīng)用),在處理不當(dāng)時(shí),就有可能會(huì)造成內(nèi)存泄露的問題。
讓我們來看看下面這個(gè)對數(shù)組進(jìn)行引用傳值的示例:
從內(nèi)存占用結(jié)果上看,雖然我們執(zhí)行了 unset($a) 方法來銷毀 $a 數(shù)組,但內(nèi)存并沒有被回收,整個(gè)處理過程的示意圖如下:
可以看到對于這塊內(nèi)存,再也沒有符合表(變量)指向了,所以 PHP 無法完成內(nèi)存回收,官方給出的解釋如下:
簡單來說就是「引用計(jì)數(shù)」算法無法檢測并釋放循環(huán)引用所使用的內(nèi)存,最終導(dǎo)致內(nèi)存泄露。
引用計(jì)數(shù)系統(tǒng)的同步周期回收
由于引用計(jì)數(shù)算法存在無法回收循環(huán)應(yīng)用導(dǎo)致的內(nèi)存泄露問題,在 PHP 5.3 之后對內(nèi)存回收的實(shí)現(xiàn)做了優(yōu)化,通過采用 引用計(jì)數(shù)系統(tǒng)的同步周期回收 算法實(shí)現(xiàn)內(nèi)存管理。引用計(jì)數(shù)系統(tǒng)的同步周期回收算法是一個(gè)改良版本的引用計(jì)數(shù)算法,它在引用基礎(chǔ)上做出了如下幾個(gè)方面的增強(qiáng):
下圖(來自 PHP 手冊),展示了新的回收算法執(zhí)行過程:
整個(gè)過程為:
采用深度優(yōu)先算法執(zhí)行:默認(rèn)刪除 > 模擬恢復(fù) > 執(zhí)行刪除 達(dá)到內(nèi)存回收的目的。
你可以從 PHP 手冊 的回收周期 了解更多,也可以閱讀文末給出的參考資料。
PHP 5 中 zval 實(shí)現(xiàn)上的主要問題:
PHP 7 中的 zval 數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的調(diào)整:
這種實(shí)現(xiàn)的優(yōu)勢:

我們在微信上24小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流