PHP的記憶體回收機制詳解
最近由於使用php編寫了一個指令碼,類比實現了一個守護進程,因此需要深入理解php中的記憶體回收機制。本文參考了PHP手冊。 在理解PHP記憶體回收機制(GC)之前,先瞭解一下變數的儲存。 php中變數存在於一個zval的變數容器中。結構如下: 類型 值 is_ref refcount zval中,除了儲存變數的類型和值之外,還有is_ref欄位和refcount欄位。 is_ref:是個bool值,用來區分變數是否屬於引用集合。什麼意思呢,你可以這麼認為:表示變數是否有一個以上的別名。 refcount:計數器,表示指向這個zval變數容器的變數個數。 兩者之間有這麼一個預設關係:當refcount值為1時,is_ref的值為false。因為refcount為1,此變數不可能有多個別名,也就不存在引用了。 安裝xdebug拓展之後,可以利用xdebug_debug_zval列印出zval容器詳情。 這裡有一點需要注意,將一個變數 = 賦值給另一個變數時,不會立即為新變數分配記憶體空間,而是在原變數的zval中給refcount加1。 只有當原變數或者發生改變時,才會為新變數分配記憶體空間,同時原變數的refcount減 1 。當然,如果unset原變數,新變數直接就使用原變數的zval而不是重新分配。 &引用賦值時,原變數的is_ref 變為1,refcount 加1. 如果給一個變數&賦值,之前 = 賦值的變數會分配空間。 運行結果如下: 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儲存的是標量,那複合類型的數組是如何儲存的呢? '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');?> 運行結果如下: a:(refcount=1, is_ref=0), array 'meaning' => (refcount=1, is_ref=0),string 'life' (length=4) 'number' => (refcount=1, is_ref=0),int 42test:(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 可以看出,數組用了比數組長度多1個zval儲存。對象類似。下面給出了數組的儲存形象表示 可以看到:數組分配了三個zval容器:a meaning number 現在看看所謂的環狀引用是如何產生的 運行結果: 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容器 是一樣的。如下: 這樣就形成了環狀引用。 在5.2及更早版本的PHP中,沒有專門的記憶體回收行程GC(Garbage Collection),引擎在判斷一個變數空間是否能夠被釋放的時候是依據這個變數的zval的refcount的值,如果refcount為0,那麼變數的空間可以被釋放,否則就不釋放,這是一種非常簡單的GC實現。 現在unset ($a),那麼array的refcount減1變為1.現在無任何變數指向這個zval,而且這個zval的計數器為1,不會回收。 儘管不再有某個範圍中的任何符號指向這個結構(就是變數容器),由於數組元素“1”仍然指向數組本身,所以這個容器不能被清除 。因為沒有另外的符號指向它,使用者沒有辦法清除這個結構,結果就會導致記憶體流失。慶幸的是,php將在請求結束時清除這個資料結構,但是在php清除之前,將耗費不少空間的記憶體。如果你要實現分析演算法,或者要做其他像一個子項目指向它的父元素這樣的事情,這種情況就會經常發生。當然,同樣的情況也會發生在對象上,實際上對象更有可能出現這種情況,因為對象總是隱式的被引用。 如果上面的情況發生僅僅一兩次倒沒什麼,但是如果出現幾千次,甚至幾十萬次的記憶體流失,這顯然是個大問題。在長時間啟動並執行指令碼,比如請求基本上不會結束的守護進程時,就會出現問題,記憶體空間會不斷耗費,導致記憶體不足而崩潰。 PHP5.3中,採用了專門的演算法(比較複雜)。,來處理環狀引用導致記憶體泄露的問題。 當一個zval可能為垃圾時,回收演算法會把這個zval放入一個記憶體緩衝區。當緩衝區達到最大臨界值時(最大值可以設定),回收演算法會迴圈遍曆所有緩衝區中的zval,判斷其是否為垃圾,並進行釋放處理。或者我們在指令碼中使用gc_collect_cycles,強制回收緩衝區中的垃圾。 在php5.3的GC中,針對的垃圾做了如下說明: 1:如果一個zval的refcount增加,那麼此zval還在使用,肯定不是垃圾,不會進入緩衝區 2:如果一個zval的refcount減少到0, 那麼zval會被立即釋放掉,不屬於GC要處理的垃圾對象,不會進入緩衝區。 3:如果一個zval的refcount減少之後大於0,那麼此zval還不能被釋放,此zval可能成為一個垃圾,將其放入緩衝區。PHP5.3中的GC針對的就是這種zval進行的處理。 開啟/關閉記憶體回收機制可以通過修改php配置實現,也可以在程式中使用gc_enable() 和 gc_disable()開啟和關閉。 開啟記憶體回收機制後,針對記憶體泄露的情況,可以節省大量的記憶體空間,但是由於記憶體回收演算法運行耗費時間,開啟記憶體回收演算法會增加指令碼的執行時間。 下面是php手冊中給的一個指令碼 self = $a; if ( $i % 500 === 0 ) { echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n"; }}?> 針對這個指令碼,給出了其在php5.2和5.3中記憶體的佔用情況,如: 針對下面這個指令碼 self = $a;} echo memory_get_peak_usage(), "\n";?> 開啟記憶體回收機制,相對於不開啟的時候,指令碼執行時間增加了7% 通常,PHP中的記憶體回收機制,僅僅在迴圈回收演算法確實運行時會有時間消耗上的增加。但是在平常的(更小的)指令碼中應根本就沒有效能影響。
http://www.bkjia.com/PHPjc/938943.htmlwww.bkjia.comtruehttp://www.bkjia.com/PHPjc/938943.htmlTechArticlePHP的記憶體回收機制詳解 最近由於使用php編寫了一個指令碼,類比實現了一個守護進程,因此需要深入理解php中的記憶體回收機制。本文參考了...