每一個php變量存在一個叫"zval"的變量容器中。一個zval變量容器,除了包含變量的類型和值,還包括兩個字節的額外信息。第一個 是"is_ref",是個bool值,用來標識這個變量是不是屬於引用集合(reference set)。經過這個字節,php引擎才能把普通變量和引用變量區分開來,因爲php容許用戶經過使用&來使用自定義引用,zval變量容器中還有 一個內部引用計數機制,來優化內存使用。第二個額外字節是"refcount",用以表示指向這個zval變量容器的變量(也稱符號即symbol)個 數。全部的符號存在一個符號表中,其中每一個符號都有做用域(scope),那些主腳本(好比:經過瀏覽器請求的的腳本)和每一個函數或者方法也都有做用域。 php
當一個變量被賦常量值時,就會生成一個zval變量容器,以下例這樣: 算法
Example #1 Creating a new zval container 數組
在上例中,新的變量a,是在當前做用域中生成的。而且生成了類型爲 string 和值爲new string的變量容器。在額外的兩個字節信息中,"is_ref"被默認設置爲 FALSE,由於沒有任何自定義的引用生成。"refcount" 被設定爲 1,由於這裏只有一個變量使用這個變量容器. 注意到當"refcount"的值是1時,"is_ref"的值老是FALSE. 若是你已經安裝了» Xdebug,你能經過調用函數 xdebug_debug_zval()顯示"refcount"和"is_ref"的值。 瀏覽器
Example #2 Displaying zval information 服務器
以上例程會輸出: 數據結構
a: (refcount=1, is_ref=0)='new string'
把一個變量賦值給另外一變量將增長引用次數(refcount). 函數
Example #3 Increasing refcount of a zval 單元測試
以上例程會輸出: 測試
a: (refcount=2, is_ref=0)='new string'
這時,引用次數是2,由於同一個變量容器被變量a和變量b關聯.當不必時,php不會去複製已生成的變量容器。變量容器在」refcount「變成0時就被銷燬. 當任何關聯到某個變量容器的變量離開它的做用域(好比:函數執行結束),或者對變量調用了函數 unset()時,」refcount「就會減1,下面的例子就能說明: 優化
Example #4 Decreasing zval refcount
以上例程會輸出:
a: (refcount=3, is_ref=0)='new string' a: (refcount=1, is_ref=0)='new string'
若是咱們如今執行 unset($a);,包含類型和值的這個變量容器就會從內存中刪除。
當考慮像 array和object這樣的複合類型時,事情就稍微有點複雜. 與 標量(scalar)類型的值不一樣,array和 object類型的變量把它們的成員或屬性存在本身的符號表中。這意味着下面的例子將生成三個zval變量容器。
Example #5 Creating a array zval
以上例程的輸出相似於:
a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=1, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42 )
Or graphically
這三個zval變量容器是:a,meaning和number。增長和減小」refcount」的規則和上面提到的同樣. 下面, 咱們在數組中再添加一個元素,而且把它的值設爲數組中已存在元素的值:
Example #6 Adding already existing element to an array
以上例程的輸出相似於:
a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=2, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42, 'life' => (refcount=2, is_ref=0)='life' )
Or graphically
從以上的xdebug輸出信息,咱們看到原有的數組元素和新添加的數組元素關聯到同一個"refcount"2的zval變量容器. 儘管 Xdebug的輸出顯示兩個值爲'life'的 zval 變量容器,實際上是同一個。 函數xdebug_debug_zval()不顯示這個信息,可是你能經過顯示內存指針信息來看到。
刪除數組中的一個元素,就是相似於從做用域中刪除一個變量. 刪除後,數組中的這個元素所在的容器的「refcount」值減小,一樣,當「refcount」爲0時,這個變量容器就從內存中被刪除,下面又一個例子能夠說明:
Example #7 Removing an element from an array
以上例程的輸出相似於:
a: (refcount=1, is_ref=0)=array ( 'life' => (refcount=1, is_ref=0)='life' )
如今,當咱們添加一個數組自己做爲這個數組的元素時,事情就變得有趣,下個例子將說明這個。例中咱們加入了引用操做符,不然php將生成一個複製。
Example #8 Adding the array itself as an element of it self
以上例程的輸出相似於:
a: (refcount=2, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=... )
Or graphically
能看到數組變量 (a) 同時也是這個數組的第二個元素(1) 指向的變量容器中「refcount」爲 2。上面的輸出結果中的"..."說明發生了遞歸操做, 顯然在這種狀況下意味着"..."指向原始數組。
跟剛剛同樣,對一個變量調用unset,將刪除這個符號,且它指向的變量容器中的引用次數也減1。因此,若是咱們在執行完上面的代碼後,對變量$a調用unset, 那麼變量$a和數組元素 "1" 所指向的變量容器的引用次數減1, 從"2"變成"1". 下例能夠說明:
Example #9 Unsetting$a
(refcount=1, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=1, is_ref=1)=... )
Or graphically
儘管再也不有某個做用域中的任何符號指向這個結構(就是變量容器),因爲數組元素「1」仍然指向數組自己,因此這個容器不能被清除 。由於沒有另外的符號指向它,用戶沒有辦法清除這個結構,結果就會致使內存泄漏。慶幸的是,php將在請求結束時清除這個數據結構,可是在php清除之 前,將耗費很多空間的內存。若是你要實現分析算法,或者要作其餘像一個子元素指向它的父元素這樣的事情,這種狀況就會常常發生。固然,一樣的狀況也會發生 在對象上,實際上對象更有可能出現這種狀況,由於對象老是隱式的被引用。
若是上面的狀況發生僅僅一兩次倒沒什麼,可是若是出現幾千次,甚至幾十萬次的內存泄漏,這顯然是個大問題。在長時間運行的腳本,好比請求基本上不會結束的 守護進程(deamons)或者單元測試中的大的套件(sets)中,在給 eZ 組件庫的模板組件作單元測試時,後者(指單元測試中的大的套件)就會出現問題.它將須要耗用2GB的內存,而通常的測試服務器沒有這麼大的內存空間。