在理解PHP垃圾回收機(jī)制(GC)之前,先了解一下變量的存儲(chǔ)。
php中變量存在于一個(gè)zval的變量容器中食铐。結(jié)構(gòu)如下:
zval中,除了存儲(chǔ)變量的類(lèi)型和值之外僧鲁,還有is_ref字段和refcount字段虐呻。
- is_ref:是個(gè)bool值象泵,用來(lái)區(qū)分變量是否屬于引用集合。什么意思呢斟叼,你可以這么認(rèn)為:表示變量是否有一個(gè)以上的別名偶惠。
- refcount:計(jì)數(shù)器,表示指向這個(gè)zval變量容器的變量個(gè)數(shù)朗涩。
兩者之間有這么一個(gè)默認(rèn)關(guān)系:當(dāng)refcount值為1時(shí)洲鸠,is_ref的值為false。因?yàn)閞efcount為1馋缅,此變量不可能有多個(gè)別名,也就不存在引用了绢淀。
<?php
$a = 1;
xdebug_debug_zval('a');
echo PHP_EOL;
$b = $a;
xdebug_debug_zval('a');
echo PHP_EOL;
$c = &$a;
xdebug_debug_zval('a');
echo PHP_EOL;
xdebug_debug_zval('b');
echo PHP_EOL;
?>
運(yùn)行結(jié)果如下:
a:(refcount=1, is_ref=0),int 1
a:(refcount=2, is_ref=0),int 1
a:(refcount=2, is_ref=1),int 1
b:(refcount=1, is_ref=0),int 1
上面描述的zval存儲(chǔ)的是標(biāo)量萤悴,那復(fù)合類(lèi)型的數(shù)組是如何存儲(chǔ)的呢?
<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
echo PHP_EOL;
class Test{
public $a = 1;
public $b = 2;
function handle(){
echo 'hehe';
}
}
$test = new Test();
xdebug_debug_zval('test');
?>
運(yùn)行結(jié)果如下:
a:(refcount=1, is_ref=0),
array
'meaning' => (refcount=1, is_ref=0),
string
'life' (length=4)
'number' => (refcount=1, is_ref=0),
int
42
test:(refcount=1, is_ref=0),
object(Test)[1]
public 'a' => (refcount=2, is_ref=0),
int
1
public 'b' => (refcount=2, is_ref=0),
int
2
可以看出皆的,數(shù)組用了比數(shù)組長(zhǎng)度多1個(gè)zval存儲(chǔ)覆履。對(duì)象類(lèi)似。下面給出了數(shù)組的存儲(chǔ)形象表示
可以看到:數(shù)組分配了三個(gè)zval容器:a meaning number
現(xiàn)在看看所謂的環(huán)狀引用是如何生成的
<?php
$a = array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );
?>
運(yùn)行結(jié)果:
a:(refcount=2, is_ref=1),
array
0 => (refcount=1, is_ref=0),
string
'one' (length=3)
1 => (refcount=2, is_ref=1), &array
a 和 1 的zval容器 是一樣的费薄。如下:
這樣就形成了環(huán)狀引用硝全。
在5.2及更早版本的PHP中,沒(méi)有專(zhuān)門(mén)的垃圾回收器GC(Garbage Collection)楞抡,引擎在判斷一個(gè)變量空間是否能夠被釋放的時(shí)候是依據(jù)這個(gè)變量的zval的refcount的值伟众,如果refcount為0,那么變量的空間可以被釋放召廷,否則就不釋放凳厢,這是一種非常簡(jiǎn)單的GC實(shí)現(xiàn)。
現(xiàn)在unset ($a),那么array的refcount減1變?yōu)?.現(xiàn)在無(wú)任何變量指向這個(gè)zval竞慢,而且這個(gè)zval的計(jì)數(shù)器為1先紫,不會(huì)回收。
盡管不再有某個(gè)作用域中的任何符號(hào)指向這個(gè)結(jié)構(gòu)(就是變量容器)筹煮,由于數(shù)組元素“1”仍然指向數(shù)組本身遮精,所以這個(gè)容器不能被清除 。因?yàn)闆](méi)有另外的符號(hào)指向它败潦,用戶沒(méi)有辦法清除這個(gè)結(jié)構(gòu)本冲,結(jié)果就會(huì)導(dǎo)致內(nèi)存泄漏。慶幸的是变屁,php將在請(qǐng)求結(jié)束時(shí)清除這個(gè)數(shù)據(jù)結(jié)構(gòu)眼俊,但是在php清除之前,將耗費(fèi)不少空間的內(nèi)存粟关。如果你要實(shí)現(xiàn)分析算法疮胖,或者要做其他像一個(gè)子元素指向它的父元素這樣的事情环戈,這種情況就會(huì)經(jīng)常發(fā)生。當(dāng)然澎灸,同樣的情況也會(huì)發(fā)生在對(duì)象上院塞,實(shí)際上對(duì)象更有可能出現(xiàn)這種情況,因?yàn)閷?duì)象總是隱式的被引用性昭。
如果上面的情況發(fā)生僅僅一兩次倒沒(méi)什么拦止,但是如果出現(xiàn)幾千次,甚至幾十萬(wàn)次的內(nèi)存泄漏糜颠,這顯然是個(gè)大問(wèn)題汹族。在長(zhǎng)時(shí)間運(yùn)行的腳本,比如請(qǐng)求基本上不會(huì)結(jié)束的守護(hù)進(jìn)程時(shí)其兴,就會(huì)出現(xiàn)問(wèn)題顶瞒,內(nèi)存空間會(huì)不斷耗費(fèi),導(dǎo)致內(nèi)存不足而崩潰元旬。
PHP5.3中榴徐,采用了專(zhuān)門(mén)的算法(比較復(fù)雜)。匀归,來(lái)處理環(huán)狀引用導(dǎo)致內(nèi)存泄露的問(wèn)題坑资。
當(dāng)一個(gè)zval可能為垃圾時(shí),回收算法會(huì)把這個(gè)zval放入一個(gè)內(nèi)存緩沖區(qū)穆端。當(dāng)緩沖區(qū)達(dá)到最大臨界值時(shí)(最大值可以設(shè)置)袱贮,回收算法會(huì)循環(huán)遍歷所有緩沖區(qū)中的zval,判斷其是否為垃圾体啰,并進(jìn)行釋放處理字柠。或者我們?cè)谀_本中使用gc_collect_cycles,強(qiáng)制回收緩沖區(qū)中的垃圾狡赐。
在php5.3的GC中窑业,針對(duì)的垃圾做了如下說(shuō)明:
1:如果一個(gè)zval的refcount增加,那么此zval還在使用枕屉,肯定不是垃圾常柄,不會(huì)進(jìn)入緩沖區(qū)
2:如果一個(gè)zval的refcount減少到0, 那么zval會(huì)被立即釋放掉搀擂,不屬于GC要處理的垃圾對(duì)象西潘,不會(huì)進(jìn)入緩沖區(qū)。
3:如果一個(gè)zval的refcount減少之后大于0哨颂,那么此zval還不能被釋放喷市,此zval可能成為一個(gè)垃圾,將其放入緩沖區(qū)威恼。PHP5.3中的GC針對(duì)的就是這種zval進(jìn)行的處理品姓。
開(kāi)啟垃圾回收機(jī)制后寝并,針對(duì)內(nèi)存泄露的情況,可以節(jié)省大量的內(nèi)存空間腹备,但是由于垃圾回收算法運(yùn)行耗費(fèi)時(shí)間衬潦,開(kāi)啟垃圾回收算法會(huì)增加腳本的執(zhí)行時(shí)間。