參考文章:垃圾回收
說在最前面:垃圾回收就是 處理無用的變量及其所關(guān)聯(lián)內(nèi)存的一種操作含友。
1. 基本思想
想說清楚PHP的垃圾回收機制吩蔑,需要從變量的實現(xiàn)說起(詳細說明請查看)钮热。變量的值存儲到以下所示zval結(jié)構(gòu)體中:
typedef struct _zval_struct zval;
...
struct _zval_struct {
zvalue_value value; /* 變量值 */
zend_uchar type; /* 變量類型 */
zend_uint refcount__gc; /* 引用計數(shù),默認值為1 */
zend_uchar is_ref__gc; /* 是否被引用烛芬,默認值為0 */
};
當一個變量$a
被賦值時就會生成一個zval變量容器隧期,此時refcount__gc=1,當$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'
當變量離開它的作用域(比如:函數(shù)執(zhí)行結(jié)束)遣臼,或者對變量調(diào)用了函數(shù) unset()時性置,”refcount“就會減1,當”refcount“減為0時揍堰,則變量容器被銷毀鹏浅,內(nèi)存被回收。
2. 新老垃圾回收方式對比
PHP5.2中的垃圾回收算法——Reference Counting :
PHP5.2中使用的內(nèi)存回收算法是大名鼎鼎的Reference Counting屏歹,中文翻譯叫做“引用計數(shù)”隐砸,其思想非常直觀和簡潔:為每個內(nèi)存對象分配一個計數(shù)器,當一個內(nèi)存對象建立時計數(shù)器初始化為1(因此此時總是有一個變量引用此對象)蝙眶,以后每有一個新變量引用此內(nèi)存對象季希,則計數(shù)器加1,而每當減少一個引用此內(nèi)存對象的變量則計數(shù)器減1,當垃圾回收機制運作的時候式塌,將所有計數(shù)器為0的內(nèi)存對象銷毀并回收其占用的內(nèi)存武通。
缺點: 這種方式雖然簡單直觀,實現(xiàn)方便珊搀,但卻存在一個致命的缺陷冶忱,就是容易造成內(nèi)存泄露,就是當兩個或多個對象互相引用形成環(huán)狀后境析,內(nèi)存對象的計數(shù)器則不會消減為0囚枪;這時候,這一組內(nèi)存對象已經(jīng)沒用了劳淆,但是不能回收链沼,從而導(dǎo)致內(nèi)存泄露;沛鸵。
為解決環(huán)形引用導(dǎo)致的垃圾括勺,產(chǎ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容器,當根緩沖區(qū)滿或內(nèi)存不足時吊趾,PHP就會執(zhí)行垃圾回收宛裕。為了避免每次變量的refcount減少的時候都調(diào)用算法進行垃圾判斷,算法會先把準則3情況下的zval節(jié)點放入節(jié)點緩沖區(qū)论泛,并標記成紫色揩尸,確保每個節(jié)點在緩沖區(qū)中只出現(xiàn)一次。
算法會分以下三步進行:MarkRoots()屁奏、ScanRoots()岩榆、CollectRoots()
- 從buffer鏈表的roots開始遍歷,把當前value標為灰色了袁,然后對當前value的成員進行深度優(yōu)先遍歷朗恳,把成員value的refcount減1,并且也標為灰色载绿;
- 重復(fù)遍歷buffer鏈表粥诫,檢查當前value引用是否為0,為0則表示確實是垃圾崭庸,把它標為白色怀浆,如果不為0則排除了引用全部來自自身成員的可能谊囚,表示還有外部的引用,并不是垃圾执赡,這時候因為步驟(1)對成員進行了refcount減1操作镰踏,需要再還原回去,對所有成員進行深度遍歷沙合,把成員refcount加1奠伪,同時標為黑色;
- 再次遍歷buffer鏈表首懈,將非白色的節(jié)點從roots鏈表中刪除绊率,最終roots鏈表中全部為真正的垃圾,最后將這些垃圾清除究履。
PHP5.3的垃圾回收算法特性總結(jié):
1滤否、并不是每次refcount減少時都進入回收周期,只有根緩沖區(qū)滿額后在開始垃圾回收最仑。
2藐俺、可以解決循環(huán)引用問題。
3泥彤、可以總將內(nèi)存泄露保持在一個閾值以下欲芹。注意:同步周期回收算法 原文中的3.1節(jié) Pseudocode and Explanatio,對算法有詳細描述
image.png
3. 與垃圾回收算法相關(guān)的PHP配置
可以通過修改php.ini中的zend.enable_gc來打開或關(guān)閉PHP的垃圾回收機制
或通過調(diào)用gc_enable()打開全景、gc_disable()關(guān)閉
或手工調(diào)用gc_collect_cycles()函數(shù)強制執(zhí)行內(nèi)存回收耀石。
4. 垃圾回收機制對性能的影響
PHP中的垃圾回收機制,僅僅在循環(huán)回收算法運行時會有時間消耗上的增加爸黄。但是在平常的(更小的)腳本中根本就沒有性能影響。
但是揭鳞,在平常腳本中有循環(huán)回收機制運行的情況下炕贵,內(nèi)存的節(jié)省將允許更多這種腳本同時運行在你的服務(wù)器上。因為總共使用的內(nèi)存沒達到上限野崇,這種好處在長時間運行腳本中尤其明顯称开。
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)存泄露堆積后果很嚴重,無論多少內(nèi)存,遲早會被占光狞膘。