首頁  >  文章  >  後端開發  >  PHP垃圾回收機制—引用計數的基本知識

PHP垃圾回收機制—引用計數的基本知識

伊谢尔伦
伊谢尔伦原創
2016-11-22 10:02:511207瀏覽

每個php變數存在一個叫做"zval"的變數容器中。一個zval變數容器,除了包含變數的類型和值,還包括兩個位元組的額外資訊。第一個是"is_ref",是個bool值,用來識別這個變數是否是屬於引用集合(reference set)。透過這個位元組,php引擎才能把普通變數和引用變數區分開來,由於php允許使用者透過使用&來使用自訂引用,zval變數容器中還有一個內部引用計數機制,來優化記憶體使用。第二個額外位元組是"refcount",用以表示指向這個zval變數容器的變數(也稱為符號即symbol)個數。所有的符號存在一個符號表中,其中每個符號都有作用域(scope),那些主腳本(例如:透過瀏覽器請求的的腳本)和每個函數或方法也都有作用域。

當一個變數被賦化常數值時,就會產生一個zval變數容器,如下例這樣:

Example #1 建立一個新的zval容器

<?php
    $a = "new string";
?>

在上例中,新的變數a,是在當前作用域中產生的。並且產生了類型為 string 和值為new string的變數容器。在額外的兩個位元組資訊中,"is_ref"被預設為 FALSE,因為沒有任何自訂的引用產生。 "refcount" 設定為 1,因為這裡只有一個變數使用這個變數容器. 注意到當"refcount"的值是1時,"is_ref"的值總是FALSE. 如果你已經安裝了Xdebug,你能通過呼叫函數 xdebug_debug_zval()顯示"refcount"和"is_ref"的值。

Example #2 顯示zval訊息

<?php
    xdebug_debug_zval(&#39;a&#39;);
?>

以上程式會輸出:

a: (refcount=1, is_ref=0)=&#39;new string&#39;

把一個變數賦值給另一個變數會增加引用次數(refcount).

Example #3 zval中refcount的成長次數(refcount).

Example #3 zval中refcount的成長

例程會輸出:

<?php
    $a = "new string";
    $b = $a;
    xdebug_debug_zval( &#39;a&#39; );
?>

這時,引用次數是2,因為同一個變數容器被變數 a 和變數 b關聯.當沒必要時,php不會去複製已產生的變數容器。變數容器在」refcount「變成0時就被銷毀. 當任何關聯到某個變數容器的變數離開它的作用域(例如:函數執行結束),或是對變數呼叫了函數 unset()時,」refcount 「就會減1,下面的例子就能說明:

Example #4 zval中refcount的減少

a: (refcount=2, is_ref=0)=&#39;new string&#39;

以上例程會輸出:

<?php
    $a = "new string";
    $c = $b = $a;
    xdebug_debug_zval( &#39;a&#39; );
    unset( $b, $c );
    xdebug_debug_zval( &#39;a&#39; );
?>

如果我們現在執行 unset($a);,包含類型和值的這個變數容器就會從記憶體中刪除。

複合類型(Compound Types)

當考慮像 array和object這樣的複合類型時,事情就稍微有點複雜。與 標量(scalar)類型的值不同,array和 object類型的變數把它們的成員或屬性存在自己的符號表中。這意味著下面的例子將產生三個zval變數容器。

Example #5 建立一個陣列zval

a: (refcount=3, is_ref=0)='new string'
a: (refcount=1, is_ref=0)=&#39;new string&#39;

以上程式的輸出類似:

<?php
    $a = array( &#39;meaning&#39; => &#39;life&#39;, &#39;number&#39; => 42 );
    xdebug_debug_zval( &#39;a&#39; );
?>

Example #6 增加一個已存在的元素到陣列中

a: (refcount=1, is_ref=0)=array (
   &#39;meaning&#39; => (refcount=1, is_ref=0)=&#39;life&#39;,
   &#39;number&#39; => (refcount=1, is_ref=0)=42
)

以上程式的輸出類似於:

rerereee

圖形化顯示如下:

 從以上的xdebug輸出訊息,我們看到原有的陣列元素和新加入的陣列元素關聯到同一個"refcount"2的zval變數容器. 儘管Xdebug的輸出顯示兩個值為'life'的zval 變數容器,其實是同一個。 函數 xdebug_debug_zval()不顯示這個訊息,但是你能透過顯示記憶體指標資訊來看到。

刪除數組中的一個元素,就是類似於從作用域中刪除一個變數. 刪除後,數組中的這個元素所在的容器的“refcount”值減少,同樣,當“refcount”為0時,這個變數容器就從記憶體中被刪除,下面又一個例子可以說明:

Example #7 從數組中移除元素

<?php
    $a = array( &#39;meaning&#39; => &#39;life&#39;, &#39;number&#39; => 42 );
    $a[&#39;life&#39;] = $a[&#39;meaning&#39;];
    xdebug_debug_zval( &#39;a&#39; );
?>

以上例程的輸出類似於:

a: (refcount=1, is_ref=0)=array (
   &#39;meaning&#39; => (refcount=2, is_ref=0)=&#39;life&#39;,
   &#39;number&#39; => (refcount=1, is_ref=0)=42,
   &#39;life&#39; => (refcount=2, is_ref=0)=&#39;life&#39;
)

現在,當我們添加一個數組本身作為這個陣列的元素時,事情就變得有趣,下個例子將說明這個。範例中我們加入了引用操作符,否則php將產生一個複製。

Example #8 加入陣列元素到陣列本身

<?php
    $a = array( &#39;meaning&#39; => &#39;life&#39;, &#39;number&#39; => 42 );
    $a[&#39;life&#39;] = $a[&#39;meaning&#39;];
    unset( $a[&#39;meaning&#39;], $a[&#39;number&#39;] );
    xdebug_debug_zval( &#39;a&#39; );
?>

以上程式的輸出類似:

a: (refcount=1, is_ref=0)=array (
   &#39;life&#39; => (refcount=1, is_ref=0)=&#39;life&#39;
)

Or graphically

 能看到陣列變數(a) 同時也是這個陣列的第二個元素(1)指向的變數容器中「refcount」為 2。上面的輸出結果中的"..."說明發生了遞歸操作, 顯然在這種情況下意味著"..."指向原始數組。

跟剛剛一樣,對一個變數呼叫unset,將刪除這個符號,且它指向的變數容器中的引用次數也減1。所以,如果我們在執行上面的程式碼後,對變數$a調用unset, 那麼變數 $a和陣列元素"1" 所指向的變數容器的引用次數減1, 從"2"變成"1".下例可以說明:

Example #9 銷毀 $a

<?php
    $a = array( &#39;one&#39; );
    $a[] =& $a;
    xdebug_debug_zval( &#39;a&#39; );
?>

或圖形化顯示如下:🎜

 清理變數容器的問題(Cleanup Problems)

儘管不再有某個作用域中的任何符號指向這個結構(就是變數容器),由於數組元素「1」仍然指向數組本身,所以這個容器不能被清除。因為沒有另外的符號指向它,使用者沒有辦法清除這個結構,結果就會導致記憶體洩漏。慶幸的是,php將在請求結束時清除這個資料結構,但是在php清除之前,將耗費不少空間的記憶體。如果你要實作分析演算法,或是要做其他像一個子元素指向它的父元素這樣的事情,這種情況就會經常發生。當然,同樣的情況也會發生在物件上,實際上物件更有可能出現這種情況,因為物件總是隱式的被引用。

如果上面的情況發生僅僅一兩次倒沒什麼,但是如果出現幾千次,甚至幾十萬次的內存洩漏,這顯然是個大問題。在長時間運行的腳本,例如請求基本上不會結束的守護程序(deamons)或者單元測試中的大的套件(sets)中,在給eZ 組件庫的模板組件做單元測試時,後者(指單元測試中的大的套件)就會出現問題.它將需要耗用2GB的內存,而一般的測試伺服器沒有這麼大的內存空間。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn