聊聊~PHP的GC

首先要說明,如果是普通類型的變量(int、float、boolean),當你執行,unset的時候,是直接刪除的。
PHP垃圾回收機制(Garbage Conllector 簡稱 GC)在PHP中,沒有任何變量指向這個對象時,這個對象就是垃圾。PHP會將其在內存中銷燬;這是PHP的GC垃圾處理機制,防止內存溢出。當一個PHP線程結束時,當前佔用的所有內存空間都會被銷燬,當前程序中所有對象同時被銷燬。
php7的垃圾回收包含兩個部分,一個是垃圾收集器,一個是垃圾回收算法。
垃圾收集器,把剛剛提到的,可能是垃圾的元素收集到回收池中 也就是把變量的 zend_refcount的信息 放在回收池中。 當回收池的值達到一定額度了,會進行統一處理。
處理的過程呢,就比較簡單。
遍歷回收池中的每一個變量,根據每一個變量,再遍歷每一個成員,如果成員還有嵌套的話繼續遍歷。然後把所有成員的 做模擬的 refcount -1。如果此時外部的變量的 引用次數爲 0 。那麼可以視爲垃圾,清楚。如果大於0,那麼恢復引用次數,並從垃圾回收池中取出。
引用計數基本知識
每個PHP變量存在一個叫做’zval’的變量容器中。一個zval變量容器,除了包含變量的類型和值,還包括兩個字節的額外信息,第一個是’is_ref’,它是個bool值,用來表示這個變量是否爲引用集合(reference set)。通過這個字節,PHP引擎才能把普通變量和引用變量區分開。由於php允許用戶通過使用&來使用自定義引用,zval變量容器中還有一個內部引用計數機制來優化內存使用。第二個額外字節是"refcount",用來表示指向這個zval變量容器的變量(也稱符號即symbol)個數。
舉個例子:

<?php
$hxg = "壞小哥";
?>

上面變量hxg,是在當前作用域生成的。並且生成類型爲string和值爲new string 的變量容器。此時,'is_ref’被設置爲FALSE,因爲沒有任何自定義的引用生成。'refcount’被設定爲1,因爲這裏只有一個變量使用這個變量容器。並且當’refcount’爲1的時候,'is_ref’總是爲FALSE。我們安裝Xdebug,調用 xdebug_debug_zval()顯示"refcount"和"is_ref"的值。

<?php
xdebug_debug_zval('hxg');
?>

上面程序會輸出===>hxg: (refcount=1, is_ref=0)=‘壞小哥’。把一個變量賦值給另一個變量將增加引用次數(refcount)。
例如增加一個引用次數:

$hxg = "壞小哥";
$ztf = $hxg;
xdebug_debug_zval( 'hxg' );

上面程序會輸出===>hxg: (refcount=2, is_ref=0)=‘壞小哥’
此時,引用次數是2,因爲同一個變量容器被變量 a 和變量 b關聯.當沒必要時,php不會去複製已生成的變量容器。變量容器在”refcount“變成0時就被銷燬. 當任何關聯到某個變量容器的變量離開它的作用域(比如:函數執行結束),或者對變量調用了函數 unset()時,”refcount“就會減1。

<?php
$hxg = "壞小哥";
$lj = $ztf = $hxg;
xdebug_debug_zval( 'hxg' );
unset( $ztf, $lj );
xdebug_debug_zval( 'hxg' );
?>

上面程序輸出:
hxg: (refcount=3, is_ref=0)=‘壞小哥’
hxg: (refcount=1, is_ref=0)=‘壞小哥’
如果我們現在執行 unset($hxg);,包含類型和值的這個變量容器就會從內存中刪除。

複合類型:
array和 object類型的變量把它們的成員或屬性存在自己的符號表中。這意味着下面的例子將生成三個zval變量容器。

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

上面程序輸出:
a: (refcount=1, is_ref=0)=array (
‘meaning’ => (refcount=1, is_ref=0)=‘life’,
‘number’ => (refcount=1, is_ref=0)=42
)
在這裏插入圖片描述
這三個zval變量容器是: a,meaning和 number。增加和減少”refcount”的規則和上面提到的一樣. 下面, 我們在數組中再添加一個元素,並且把它的值設爲數組中已存在元素的值:

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

上面程序輸出===>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’
)
在這裏插入圖片描述
我們看到原有的數組元素和新添加的數組元素關聯到同一個"refcount"2的zval變量容器. 儘管 Xdebug的輸出顯示兩個值爲’life’的 zval 變量容器,其實是同一個。
刪除數組中的一個元素,就是類似於從作用域中刪除一個變量. 刪除後,數組中的這個元素所在的容器的“refcount”值減少,同樣,當“refcount”爲0時,這個變量容器就從內存中被刪除。

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

上面程序輸出===> a: (refcount=1, is_ref=0)=array (
‘life’ => (refcount=1, is_ref=0)=‘life’
)

好了,現在開始討論一個重要的問題,如果一個數組,引用自己怎麼處理?

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

上面程序輸出===>a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)=‘one’,
1 => (refcount=2, is_ref=1)=…
)
在這裏插入圖片描述
能看到數組變量 (a) 同時也是這個數組的第二個元素(1) 指向的變量容器中“refcount”爲 2。上面的輸出結果中的"…“說明發生了遞歸操作, 顯然在這種情況下意味着”…"指向原始數組。
PHP5.3.0之後解決循環引用問題
如果引用計數減少到零,所在變量容器將被清除(free),不屬於垃圾
如果一個zval 的引用計數減少後還大於0,那麼它會進入垃圾週期。僅僅在引用計數減少到非零值時,纔會產生垃圾週期(garbage cycle)。其次,在一個垃圾週期中,通過檢查引用計數是否減1,並且檢查哪些變量容器的引用次數是零,來發現哪部分是垃圾。
在這裏插入圖片描述
A: 把所有可能根(possible roots 都是zval變量容器),放在根緩衝區(root buffer)中(用紫色來標記),保證每個可能的垃圾根在緩衝區只出現一次。當緩衝區滿了,進行垃圾回收操作。
B: 模擬刪除每個紫色變量,算法以深度優先對每一個節點所包含的zval進行減1操作(在對對類遍歷時,將繼承類和自己的兩個對象成員屬性表進行合併),爲了確保不會對同一個zval的refcount重複執行減1操作,一旦zval的refcount減1之後會將zval標記成灰色。需要強調的是,這個步驟中,起初節點zval本身不做減1操作,但是如果節點zval中包含的zval又指向了節點zval(環形引用),那麼這個時候需要對節點zval進行減1操作。
C: 算法再次以深度優先判斷每一個節點包含的zval的值,如果zval的refcount等於0,那麼將其標記成藍色(代表垃圾),如果zval的refcount大於0,那麼將對此zval以及其包含的zval進行refcount加1操作,這個是對非垃圾的還原操作,同時將這些zval的顏色變成黑色。
D: 刪除藍色節點。
根緩存區有固定的大小,可存10,000個可能根,當然你可以通過修改PHP源碼文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然後重新編譯PHP,來修改這個10,000值。
當然PHP也爲我們準備了手動來操作GC的函數
通過分別調用gc_enable() 和 gc_disable()函數來打開和關閉垃圾回收機制。調用這些函數,與修改配置項來打開或關閉垃圾回收機制的效果是一樣的。即使在可能根緩衝區還沒滿時,也能強制執行週期回收。你能調用gc_collect_cycles()函數達到這個目的。這個函數將返回使用這個算法回收的週期數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章