Re: [分享] PHP GC 機制

看板PHP作者 (Ricky)時間12年前 (2013/06/04 09:32), 編輯推噓2(207)
留言9則, 4人參與, 最新討論串2/2 (看更多)
※ 引述《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

06/04 16:09, , 1F
嘖嘖嘖,把舊文拿出來充數可不行唷 XDD
06/04 16:09, 1F

06/04 16:30, , 2F
哈...被發現了,看來得生點有趣的東西。
06/04 16:30, 2F

06/04 18:19, , 3F
推第二篇的GC觀念和使用說明。但小熟zend engine+只看
06/04 18:19, 3F

06/04 18:19, , 4F
第一篇的話會有誤解
06/04 18:19, 4F

06/04 22:53, , 5F
有人用$this->me=$this,也會有原PO講的情形嗎?_
06/04 22:53, 5F

06/04 22:55, , 6F
我用$test->me=$test,才會有這種情形,我看官網也是寫這樣
06/04 22:55, 6F

06/04 22:55, , 7F
不是寫在 __contruct 裡面? (我的測試環境php5.2.17)
06/04 22:55, 7F

06/05 09:07, , 8F
還沒在PHP5.2上實驗 ,不過基本的精神是環形引用。
06/05 09:07, 8F

06/05 09:10, , 9F
如果是在web server上跑,基本上沒啥 memory leak的問題。
06/05 09:10, 9F
文章代碼(AID): #1HhKE-ig (PHP)
討論串 (同標題文章)
本文引述了以下文章的的內容:
0
2
完整討論串 (本文為第 2 之 2 篇):
0
2
文章代碼(AID): #1HhKE-ig (PHP)