PHP垃圾回收機(jī)制學(xué)習(xí)記錄

參考文章:垃圾回收

說在最前面:垃圾回收就是 處理無用的變量及其所關(guān)聯(lián)內(nèi)存的一種操作

1. 基本思想

想說清楚PHP的垃圾回收機(jī)制,需要從變量的實現(xiàn)說起(詳細(xì)說明請查看)。變量的值存儲到以下所示zval結(jié)構(gòu)體中:

typedef struct _zval_struct zval;
...
struct _zval_struct {
    zvalue_value value;     /* 變量值 */
    zend_uchar type;    /* 變量類型 */
    zend_uint refcount__gc;     /* 引用計數(shù),默認(rèn)值為1 */
    zend_uchar is_ref__gc;    /* 是否被引用,默認(rèn)值為0 */
};

當(dāng)一個變量$a被賦值時就會生成一個zval變量容器,此時refcount__gc=1,當(dāng)$a再賦值給其他的變量$b時,此時不再產(chǎn)生新的zval變量容器,而是將$b指向$a的zval變量容器,如下所示:

<?php
$a = "new string";
xdebug_debug_zval( 'a' );
//輸出:a: (refcount=1, is_ref=0)='new string'
$b = $a;
xdebug_debug_zval( 'a' );
//輸出:a: (refcount=2, is_ref=0)='new string'

當(dāng)變量離開它的作用域(比如:函數(shù)執(zhí)行結(jié)束),或者對變量調(diào)用了函數(shù) unset()時,”refcount“就會減1,當(dāng)”refcount“減為0時,則變量容器被銷毀,內(nèi)存被回收。

2. 新老垃圾回收方式對比

PHP5.2中的垃圾回收算法——Reference Counting :

PHP5.2中使用的內(nèi)存回收算法是大名鼎鼎的Reference Counting,中文翻譯叫做“引用計數(shù)”,其思想非常直觀和簡潔:為每個內(nèi)存對象分配一個計數(shù)器,當(dāng)一個內(nèi)存對象建立時計數(shù)器初始化為1(因此此時總是有一個變量引用此對象),以后每有一個新變量引用此內(nèi)存對象,則計數(shù)器加1,而每當(dāng)減少一個引用此內(nèi)存對象的變量則計數(shù)器減1,當(dāng)垃圾回收機(jī)制運(yùn)作的時候,將所有計數(shù)器為0的內(nèi)存對象銷毀并回收其占用的內(nèi)存。

缺點: 這種方式雖然簡單直觀,實現(xiàn)方便,但卻存在一個致命的缺陷,就是容易造成內(nèi)存泄露,就是當(dāng)兩個或多個對象互相引用形成環(huán)狀后,內(nèi)存對象的計數(shù)器則不會消減為0;這時候,這一組內(nèi)存對象已經(jīng)沒用了,但是不能回收,從而導(dǎo)致內(nèi)存泄露;。

為解決環(huán)形引用導(dǎo)致的垃圾,產(chǎn)生了新的垃圾回收算法,遵守以下幾個基本準(zhǔn)則:
1.如果一個zval的refcount減少到0, 那么zval可以被釋放掉,不屬于垃圾
2.如果一個zval的refcount減少之后大于0,那么此zval還不能被釋放,此zval可能成為一個垃圾

PHP5.3中的新垃圾回收算法——Concurrent Cycle Collection in Reference Counted Systems :

PHP會分配一個固定大小的“根緩沖區(qū)”,這個緩沖區(qū)可存放10000個的zval容器,當(dāng)根緩沖區(qū)滿或內(nèi)存不足時,PHP就會執(zhí)行垃圾回收。為了避免每次變量的refcount減少的時候都調(diào)用算法進(jìn)行垃圾判斷,算法會先把準(zhǔn)則3情況下的zval節(jié)點放入節(jié)點緩沖區(qū),并標(biāo)記成紫色,確保每個節(jié)點在緩沖區(qū)中只出現(xiàn)一次。
算法會分以下三步進(jìn)行:MarkRoots()、ScanRoots()、CollectRoots()

  1. 從buffer鏈表的roots開始遍歷,把當(dāng)前value標(biāo)為灰色,然后對當(dāng)前value的成員進(jìn)行深度優(yōu)先遍歷,把成員value的refcount減1,并且也標(biāo)為灰色;
  2. 重復(fù)遍歷buffer鏈表,檢查當(dāng)前value引用是否為0,為0則表示確實是垃圾,把它標(biāo)為白色,如果不為0則排除了引用全部來自自身成員的可能,表示還有外部的引用,并不是垃圾,這時候因為步驟(1)對成員進(jìn)行了refcount減1操作,需要再還原回去,對所有成員進(jìn)行深度遍歷,把成員refcount加1,同時標(biāo)為黑色;
  3. 再次遍歷buffer鏈表,將非白色的節(jié)點從roots鏈表中刪除,最終roots鏈表中全部為真正的垃圾,最后將這些垃圾清除。

PHP5.3的垃圾回收算法特性總結(jié):
1、并不是每次refcount減少時都進(jìn)入回收周期,只有根緩沖區(qū)滿額后在開始垃圾回收。
2、可以解決循環(huán)引用問題。
3、可以總將內(nèi)存泄露保持在一個閾值以下。

注意:同步周期回收算法 原文中的3.1節(jié) Pseudocode and Explanatio,對算法有詳細(xì)描述

image.png

3. 與垃圾回收算法相關(guān)的PHP配置

可以通過修改php.ini中的zend.enable_gc來打開或關(guān)閉PHP的垃圾回收機(jī)制
或通過調(diào)用gc_enable()打開、gc_disable()關(guān)閉
或手工調(diào)用gc_collect_cycles()函數(shù)強(qiáng)制執(zhí)行內(nèi)存回收。

4. 垃圾回收機(jī)制對性能的影響

PHP中的垃圾回收機(jī)制,僅僅在循環(huán)回收算法運(yùn)行時會有時間消耗上的增加。但是在平常的(更小的)腳本中根本就沒有性能影響。
但是,在平常腳本中有循環(huán)回收機(jī)制運(yùn)行的情況下,內(nèi)存的節(jié)省將允許更多這種腳本同時運(yùn)行在你的服務(wù)器上。因為總共使用的內(nèi)存沒達(dá)到上限,這種好處在長時間運(yùn)行腳本中尤其明顯。

5. 內(nèi)存溢出和內(nèi)存泄漏的區(qū)別

內(nèi)存溢出 out of memory,是指程序在申請內(nèi)存時,沒有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory;比如申請了一個integer,但給它存了long才能存下的數(shù),那就是內(nèi)存溢出。
內(nèi)存泄露 memory leak,是指程序在申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很嚴(yán)重,無論多少內(nèi)存,遲早會被占光。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容