當前位置:
首頁 > 知識 > php的垃圾回收機制——引用計數

php的垃圾回收機制——引用計數

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

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

例1 生成一個新的zval容器

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

例2 顯示zval信息

以上常式會輸出:

a: (refcount=1, is_ref=0)= new string

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

例3 增加一個zval的引用計數

以上常式會輸出:

a: (refcount=2, is_ref=0)= new string

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

例4 減少引用計數

以上常式會輸出:

a: (refcount=3, is_ref=0)= new string

a: (refcount=1, is_ref=0)= new string

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

複合類型(Compound Types)

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

例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

)

圖示:

一個簡單數組的zval

這三個zval變數容器是: a,meaning和 number。增加和減少」refcount」的規則和上面提到的一樣. 下面, 我們在數組中再添加一個元素,並且把它的值設為數組中已存在元素的值:

例6 添加一個已經存在的元素到數組中

以上常式的輸出類似於:

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

)

圖示:

帶有引用的簡單數組的zval

從以上的xdebug輸出信息,我們看到原有的數組元素和新添加的數組元素關聯到同一個"refcount"2的zval變數容器. 儘管 Xdebug的輸出顯示兩個值為 life 的 zval 變數容器,其實是同一個。 函數xdebug_debug_zval()不顯示這個信息,但是你能通過顯示內存指針信息來看到。

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

例7 從數組中刪除一個元素

以上常式的輸出類似於:

a: (refcount=1, is_ref=0)=array (

life => (refcount=1, is_ref=0)= life

)

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

例8 把數組作為一個元素添加到自己

以上常式的輸出類似於:

a: (refcount=2, is_ref=1)=array (

0 => (refcount=1, is_ref=0)= one ,

1 => (refcount=2, is_ref=1)=...

)

圖示:

自引用(curcular reference,自己是自己的一個元素)的數組的zval

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

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

例9 Unsetting $a

(refcount=1, is_ref=1)=array (

0 => (refcount=1, is_ref=0)= one ,

1 => (refcount=1, is_ref=1)=...

)

圖示:

Zvals after removal of array with a circular reference demonstrating the memory leak

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

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

如果上面的情況發生僅僅一兩次倒沒什麼,但是如果出現幾千次,甚至幾十萬次的內存泄漏,這顯然是個大問題。這樣的問題往往發生在長時間運行的腳本中,比如請求基本上不會結束的守護進程(deamons)或者單元測試中的大的套件(sets)中。後者的例子:在給巨大的eZ(一個知名的PHP Library) 組件庫的模板組件做單元測試時,就可能會出現問題。有時測試可能需要耗用2GB的內存,而測試伺服器很可能沒有這麼大的內存。

GIF/281K

技術交流Q群:

聊聊技術+妹紙。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 PHP技術大全 的精彩文章:

colly-go語言編寫的CPU單核超過1k次請求的web採集利器
MultiHttp:高性能的 PHP 封裝的 HTTP Restful 多線程並發請求庫
TiddlyWiki 打造輕便個人 Wiki 知識庫
安全運維那些事之SSH
寶塔Linux面板–9月20日更新–5.2.0正式版

TAG:PHP技術大全 |