※ 本文為 dinos 轉寄自 ptt.cc 更新時間: 2013-06-04 11:41:23
看板 PHP
作者 標題 Re: [分享] PHP GC 機制
時間 Tue Jun 4 09:32:44 2013
※ 引述《rickysu (Ricky)》之銘言:
: 題外話: 搞了好久終於註冊好 PTT 了。
: ======================================
: 前陣子看到某篇文章提到,要回收物件時,
: 使用 $obj = null 會馬上回收,unset($obj) 則會比較慢。
: 在解答之前,先來玩個小遊戲。
: <?php
: class test
: {
: public function __destruct()
: {
: echo "object of test is dead\n";
: }
: }
: $test = new test();
: $test = null;
: die("program is end\n");
: 執行結果
: object of test is dead
: program is end
: 很符合結果 $test = null 先執行 GC (destruct) 接著 die output
: ===================
: <?php
: class test
: {
: protected $me;
: public function __contruct()
: {
: $this-> me = $this;
: }
: public function __destruct()
: {
: echo "object of test is dead\n";
: }
: }
: $test = new test();
: unset($test);
: die("program is end\n");
: 執行結果
: program is end
: object of test is dead
: unset 沒有進行 GC,一直到程式結束後,才開始進行 GC。
: 疑 unset 沒有進行 GC ??!!
: 好玩的事情發生了,難道真的是 unset 是看心情 GC 的??
: 其實上面的範例即使改成 $test = null; 執行結果也是一樣的。
: 這邊就先賣個關子,明天再來解答為什麼會有這個結果,以及該怎麼避免這樣的問題。
PHP 在判斷物件是否該被 GC 啟用了 reference counting 的機制來作為判斷。
簡單的說,當某個物件被參照時就把他的 refcount+1 。
例如 $a = new test();
他是把 test 物件 refcount + 1,
$a 只是一個指向 test 物件的指標。
如果這時候又有一個
$b = $a;
php 底層則是把 test 物件的 refcount 再度 + 1。
這也就是為什麼 php 從 5.0 之後物件都是 by referenc 的原因。
一方面為了節省記憶體,一方面則是加快物件引用的處理速度。
那 PHP 怎麼判斷一個物件該被執行 GC。
當一個變數被賦予新的數值,或是被 unset ,就會把他對應物件的 refcount-1。
當物件的 refcount 歸 0 時,進行 GC (先執行 destructor,再將 memory 回收)。
看起來 reference counting 的機制運作得很完美,
但是某天某位仁兄在 PHP 的 bug report 中發了一個 bug。大致的問題是這樣。
他發覺當某個情況時 PHP 會迅速的吃光所有的 memory。
(註: PHP 底層處理 array 跟 object 使用的機制是相同的,都是透過 hashtable )
while(true){
$a = array(1,2,3,4);
$a[] = &$a;
}
照理來說 $a 被重新 assign array 時,原本的 memory 應該要被釋放掉才對。
可是實際狀況卻不是這樣。 Why??
我們來看一下前面提到的第二個例子
$test = new test();
首先 constructor 中指定了
$this->me = $this; 這時候 test 物件的 refcount => 1
接著 $test = new test(); refcount = 2
然後執行 unset($test); refcount = 1,因為參照的變數被 unset 所以 -1
這時候好玩的事情發生了 refcount 尚未歸 0,所以不會被 GC ,
但是已經沒有任何變數參照到這個物件了。
這個物件就永遠變成垃圾而且無法被回收,除非程式結束才會被回收。
那這個問題在 PHP 5.2 以前的版本是無解的,幸好 PHP 大部分的狀況都是一次 request
後就結束,程式結束後 memory 還是會被 OS 回收。
但是隨著 framework 的盛行,
PHP 中物件被環形引用($a->next = $b; $b->next = $a;)的狀況越來越多,
因此 PHP 5.3 開始重新設計整個 GC 機制。
如果有仔細看過 PHP 5.3 的 release note ,除了 namespace 之外
最重大的變動就是多了 gc_collect_cycles, gc_enable, gc_disable。
PHP 5.3 中引入了一個新的演算法,專們用來對付這種環形引用所留下來的垃圾。
如果想看看他是怎運作的可以參考這篇
http://www.php.net/manual/en/features.gc.collecting-cycles.php
不過這種演算法每次都得遞迴整個引用的物件,
造成的代價相當高。(新版本跑得比之前版本慢是不被允許的)
因此PHP 5.3 會將每個可能需要被 GC 的物件先丟到一個 list 中。
這邊所謂的 "可能需要被GC" 是指,只要是物件或是array,
當他被 unreference 時就會先丟到這個暫存 list 中。
當 list 滿的時候一次進行 GC (印象中沒記錯list只能容納1000個待GC的物件)。
如果想要強制進行 GC 只要呼叫 gc_collect_cycles() ,就會馬上進行 GC。
希望這篇文章能讓大家對 PHP 的 GC 執行時機有些幫助。
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 220.130.136.115
--
※ 看板: dinos 文章推薦值: 0 目前人氣: 0 累積人氣: 91
回列表(←)
分享