前言:以前對PHP的GC只是瞭解了個大概,此次詳細瞭解下PHP的垃圾回收機制(GC)。
介於網上大部分都是PHP5.X的GC,雖然 php5 到 php7 GC部分作出的改動較小,但我以爲仍是一塊兒寫下來比較好
1、原理php
php5和php7的垃圾回收機制都是利用引用計數
2、php5和php7不一樣點算法
一、PHP5標量數據類型會計數,PHP7標量數據類型再也不計數,不須要單獨分配內存
二、PHP7的zval 須要的內存再也不是單獨從堆上分配,再也不本身存儲引用計數。
三、PHP7的複雜數據類型(好比數組和對象)的引用計數由其自身來存儲。
3、變量在zval的變量容器中結構數組
zval中,除了存儲變量的類型和值以外,還有is_ref字段和refcount字段 一、is_ref:是個bool值,用來區分變量是否屬於引用集合。 二、refcount:計數器,表示指向這個zval變量容器的變量個數。
4、PHP5.3標量在zval容器例子php7
注意:php5.3中將一個變量 = 賦值給另外一個變量時,不會當即爲新變量分配內存空間,而是在原變量的zval中給refcount加1。 只有當原變量或者發生改變時,纔會爲新變量分配內存空間,同時原變量的refcount減 1 。固然,若是unset原變量,新變量直接就使用原變量的zval而不是從新分配。&引用賦值時,原變量的is_ref 加1. 若是給一個變量&賦值,以前 = 賦值的變量會分配空間。測試
<?php $a = 1; xdebug_debug_zval('a'); echo PHP_EOL; $b = $a; xdebug_debug_zval('a'); echo PHP_EOL; $c = &$a; xdebug_debug_zval('a'); echo PHP_EOL; xdebug_debug_zval('b'); echo PHP_EOL;
結果以下:
a:(refcount=1, is_ref=0),int 1spa
a:(refcount=2, is_ref=0),int 1debug
a:(refcount=2, is_ref=1),int 1code
b:(refcount=1, is_ref=0),int 1對象
5、PHP7.X 標量在zval容器例子blog
<?php $a = 1; xdebug_debug_zval('a'); echo PHP_EOL; $b = $a; xdebug_debug_zval('a');
結果以下:能夠看到標量(布爾,字符串,整形,浮點型)再也不計數了
6、PHP5.3複合類型數組和對象在zval容器例子
<?php $a = array( 'meaning' => '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');
結果以下:能夠看出,數組用了比數組長度多1個zval存儲。數組分配了三個zval容器:a meaning number
a:(refcount=1, is_ref=0),
array
'meaning' => (refcount=1, is_ref=0),
string
'life' (length=4)
'number' => (refcount=1, is_ref=0),
int
42
test:(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
7、PHP7.X複合類型數組和對象在zval容器例子
<?php $a = array( 'meaning' => '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=2,後經測試發現數組的refcount都是從2開始的
8、循環引用問題
一、PHP7.1效果
<?php $a = array('life'); xdebug_debug_zval( 'a' ); echo PHP_EOL; $a[] = &$a; xdebug_debug_zval('a');
能夠看到,箭頭方向表示的就是遞歸循環引用了
二、再看看5.3的效果
說明:在5.2及更早版本的PHP中,沒有專門的垃圾回收器GC(Garbage Collection),引擎在判斷一個變量空間是否可以被釋放的時候是依據這個變量的zval的refcount的值,
若是refcount爲0,那麼變量的空間能夠被釋放,不然就不釋放,這是一種很是簡單的GC實現。如今unset ($a),那麼array的refcount減1變爲1.如今無任何變量指向這個zval,
並且這個zval的計數器爲1,不會回收。
結果:儘管再也不有某個做用域中的任何符號指向這個結構(就是變量容器),因爲子元素「1」仍然指向數組自己,因此這個容器不能被清除 。
由於沒有另外的符號指向它,用戶沒有辦法清除這個結構,結果就會致使內存泄漏。
在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()開啓和關閉。
9、垃圾回收算法
一、對每一個根緩衝區中的根zval按照深度優先遍歷算法遍歷全部能遍歷到的zval,並將每一個zval的refcount減1,同時爲了不對同一zval屢次減1(由於可能不一樣的根能遍歷到同一個zval),
每次對某個zval減1後就對其標記爲「已減」。
二、再次對每一個緩衝區中的根zval深度優先遍歷,若是某個zval的refcount不爲0,則對其加1,不然保持其爲0。
三、清空根緩衝區中的全部根(注意是把這些zval從緩衝區中清除而不是銷燬它們),而後銷燬全部refcount爲0的zval,並收回其內存。
若是不能徹底理解也沒有關係,只需記住PHP5.3的垃圾回收算法有如下幾點特性:
一、並非每次refcount減小時都進入回收週期,只有根緩衝區滿額後在開始垃圾回收。
二、能夠解決循環引用問題。
三、能夠總將內存泄露保持在一個閾值如下。
以上就是所有內容了