你寫了一個(gè)php腳本摇天,一般都不用考慮內(nèi)存泄露和垃圾回收的問題硫眯,因?yàn)橐话闱闆r下你的腳本很快就執(zhí)行完退出了。
但在一些運(yùn)行時(shí)間長井赌,數(shù)據(jù)量大的時(shí)候谤逼,程序運(yùn)行一段時(shí)間后,php腳本就占用了過多內(nèi)存仇穗,然后就報(bào)錯(cuò)(PHP Fatal error: Allowed memory size of 134217728 bytes exhausted)退出了流部。一般來說,每個(gè)頁面處理結(jié)束纹坐,新建的simple_html_dom對(duì)象就應(yīng)該被銷毀了——但是實(shí)際上沒有枝冀,很明顯,內(nèi)存泄露發(fā)生了耘子。
PHP的垃圾回收機(jī)制
php 5.3之前使用的垃圾回收機(jī)制是單純的“引用計(jì)數(shù)”果漾,也就是每個(gè)內(nèi)存對(duì)象都分配一個(gè)計(jì)數(shù)器,當(dāng)內(nèi)存對(duì)象被變量引用時(shí)谷誓,計(jì)數(shù)器+1绒障;當(dāng)變量引用撤掉后,計(jì)數(shù)器-1捍歪;當(dāng)計(jì)數(shù)器=0時(shí)户辱,表明內(nèi)存對(duì)象沒有被使用鸵钝,該內(nèi)存對(duì)象則進(jìn)行銷毀,垃圾回收完成庐镐。
“引用計(jì)數(shù)”存在問題恩商,就是當(dāng)兩個(gè)或多個(gè)對(duì)象互相引用形成環(huán)狀后,內(nèi)存對(duì)象的計(jì)數(shù)器則不會(huì)消減為0必逆;這時(shí)候怠堪,這一組內(nèi)存對(duì)象已經(jīng)沒用了,但是不能回收名眉,從而導(dǎo)致內(nèi)存泄露粟矿。
php5.3開始,使用了新的垃圾回收機(jī)制璧针,在引用計(jì)數(shù)基礎(chǔ)上嚷炉,實(shí)現(xiàn)了一種復(fù)雜的算法,來檢測內(nèi)存對(duì)象中引用環(huán)的存在探橱,以避免內(nèi)存泄露。
查看內(nèi)存是否泄露
看是否有該釋放的內(nèi)存沒有被釋放绘证,可以簡單的通過 調(diào)用 memory_get_usage 函數(shù)查看內(nèi)存使用情況來判斷隧膏;memory_get_usage 函數(shù)返回的內(nèi)存使用數(shù)據(jù)據(jù)說不是很準(zhǔn)確,可以使用 php 的 xdebug 擴(kuò)展來獲得更準(zhǔn)確翔實(shí)的內(nèi)存使用情況嚷那。
class A{
private $b;
function __construct(){
$this->b = new B($this);
}
function __destruct(){
//echo "A destruct\n";
}
}
class B{
private $a;
function __construct($a){
$this->a = $a;
}
function __destruct(){
//echo "B descturct\n";
}
}
for($i=0;;$i++){
$a = new A();
if($i00 == 0){
echo memory_get_usage()."\n";
}
}
上面就構(gòu)造了一個(gè)會(huì)產(chǎn)生環(huán)狀引用的例子胞枕。每次創(chuàng)建一個(gè)A對(duì)象的實(shí)例a,a就創(chuàng)建一個(gè)B對(duì)象的實(shí)例b魏宽,同時(shí)讓b引用a腐泻。這樣,每個(gè)A對(duì)象永遠(yuǎn)被一個(gè)B引用队询,而每個(gè)B對(duì)象同時(shí)被一個(gè)對(duì)象A引用派桩,引用環(huán)就這樣產(chǎn)生了。
在php5.2的環(huán)境下執(zhí)行這段代碼蚌斩,會(huì)發(fā)現(xiàn)內(nèi)存使用在單調(diào)上漲铆惑,也沒有A和B的析構(gòu)函數(shù)被執(zhí)行后輸出的“A/B desctruct”信息;直到內(nèi)存耗盡送膳,輸出“PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 40 bytes)”员魏。
在php5.3的環(huán)境下執(zhí)行這段代碼,則發(fā)現(xiàn)內(nèi)存使用在上跳下竄叠聋,但是永遠(yuǎn)沒有超過一個(gè)限額撕阎。程序也會(huì)輸出大量的“A/B desctruct”,這說明析構(gòu)函數(shù)被調(diào)用了碌补。
我的同事的程序中虏束,就存在這種引用的環(huán)路名斟,而他的腳本,實(shí)在php5.2.3下執(zhí)行的魄眉。simple_html_dom工具中砰盐,有兩個(gè)類,分別是simple_html_dom和simple_html_dom_node坑律,前者中有一個(gè)數(shù)組成員變量nodes岩梳,數(shù)組中每個(gè)元素都是一個(gè)simple_html_dom_node對(duì)象;而每個(gè)simple_html_dom_node對(duì)象都有一個(gè)成員變量dom晃择,該dom的值就是前面的simple_html_dom對(duì)象——這樣就形成了一個(gè)漂亮的引用環(huán)冀值,導(dǎo)致了內(nèi)存泄露。解決的辦法也很簡單宫屠,就是simple_html_dom對(duì)象在使用完畢時(shí)列疗,主動(dòng)調(diào)用其clear函數(shù),清空其成員變量nodes浪蹂,環(huán)就被打破了抵栈,內(nèi)存泄露也就不會(huì)發(fā)生了。
其他
- 垃圾回收的時(shí)機(jī)
PHP中坤次,引用計(jì)數(shù)為0古劲,則內(nèi)存立刻釋放。也就是說缰猴,不存在環(huán)狀引用的變量产艾,離開變量的作用域,內(nèi)存被立刻釋放滑绒。環(huán)狀引用檢測則是在滿足一定條件下觸發(fā)闷堡,所以在上面的例子中,會(huì)看到使用的內(nèi)存有大幅度的波動(dòng)疑故。也可以通過 gc_collect_cycles 函數(shù)來主動(dòng)進(jìn)行環(huán)狀引用檢測杠览。 - &符號(hào)的影響
顯式引用一個(gè)變量,會(huì)增加該內(nèi)存的引用計(jì)數(shù):
$a = "something";
$b = &$a;
此時(shí)unset($a), 但是仍有$b指向該內(nèi)存區(qū)域的引用焰扳,內(nèi)存不會(huì)釋放倦零。
- unset函數(shù)的影響
unset只是斷開一個(gè)變量到一塊內(nèi)存區(qū)域的連接,同時(shí)將該內(nèi)存區(qū)域的引用計(jì)數(shù)-1吨悍;在上面的例子中扫茅,循環(huán)體內(nèi)部,$a=new A(); unset($a);并不會(huì)將$a的引用計(jì)數(shù)減到零育瓜; - = null 操作的影響葫隙;
$a = null 是直接將$a 指向的數(shù)據(jù)結(jié)構(gòu)置空,同時(shí)將其引用計(jì)數(shù)歸0躏仇。 - 腳本執(zhí)行結(jié)束的影響
腳本執(zhí)行結(jié)束恋脚,該腳本中使用的所有內(nèi)存都會(huì)被釋放腺办,不論是否有引用環(huán)。