PHP的垃圾回收機制(建議收藏)
一、原理
php5和php7的垃圾回收機制都是利用引用計數
二、php5和php7不同點
1、PHP5標量資料型別會計數,PHP7標量資料型別不再計數,不需要單獨分配記憶體 2、PHP7的zval 需要的記憶體不再是單獨從堆上分配,不再自己儲存引用計數。 3、PHP7的複雜資料型別(比如陣列和物件)的引用計數由其自身來儲存。
三、變數在zval的變數容器中結構
zval中,除了儲存變數的型別和值之外,還有is_ref欄位和refcount欄位
1、is_ref:是個bool值,用來區分變數是否屬於引用集合。
2、refcount:計數器,表示指向這個zval變數容器的變數個數。
四、PHP5.3標量在zval容器例子
注意: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 1
a:(refcount=2, is_ref=0),int 1
a:(refcount=2, is_ref=1),int 1
b:(refcount=1, is_ref=0),int 1
五、PHP7.X 標量在zval容器例子
<?php
$a = 1;
xdebug_debug_zval('a');
echo PHP_EOL;
$b = $a;
xdebug_debug_zval('a');
結果如下:可以看到標量(布林,字串,整形,浮點型)不再計數了
六、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
test:(refcount=1, is_ref=0),
object(Test)[1]
public 'a' => (refcount=2, is_ref=0),
int
public 'b' => (refcount=2, is_ref=0),
int
七、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開始的
八、迴圈引用問題
1、PHP7.1效果
<?php
$a = array('life');
xdebug_debug_zval( 'a' );
echo PHP_EOL;
$a[] = &$a;
xdebug_debug_zval('a');
可以看到,箭頭方向表示的就是遞迴迴圈引用了
說明:在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()開啟和關閉。
九、垃圾回收演算法
1、對每個根緩衝區中的根zval按照深度優先遍歷演算法遍歷所有能遍歷到的zval,並將每個zval的refcount減1,同時為了避免對同一zval多次減1(因為可能不同的根能遍歷到同一個zval),
每次對某個zval減1後就對其標記為“已減”。
2、再次對每個緩衝區中的根zval深度優先遍歷,如果某個zval的refcount不為0,則對其加1,否則保持其為0。
3、清空根緩衝區中的所有根(注意是把這些zval從緩衝區中清除而不是銷燬它們),然後銷燬所有refcount為0的zval,並收回其記憶體。
如果不能完全理解也沒有關係,只需記住PHP5.3的垃圾回收演算法有以下幾點特性:
1、並不是每次refcount減少時都進入回收週期,只有根緩衝區滿額後在開始垃圾回收。
2、可以解決迴圈引用問題。
3、可以總將記憶體洩露保持在一個閾值以下。
以上內容希望幫助到大家,更多PHP大廠PDF面試文件,PHP進階架構視訊資料,PHP精彩好文免費獲取可以關注公眾號:PHP開源社群,或者訪問:
「其他文章」
- 基於Nginx的負載均衡原理與實戰
- PHP控制反轉(IOC)和依賴注入(DI)
- 深入理解PHP7核心之Reference
- php中類的不定引數使用示例
- php單例模式的常見應用場景
- laravel 配置MySQL讀寫分離
- PHP的垃圾回收機制(建議收藏)
- 【shell指令碼】字串和陣列的使用
- PHP-FPM是什麼東東?
- PHP 編寫守護程序
- PHP命令列指令碼接收傳入引數的三種方式
- php專案中類的自動載入
- 複習下Linux去除重複項命令uniq
- 深入理解PHP核心:變數及資料型別
- Swoole協程與傳統fpm同步模式比較
- PHP中Session ID的實現原理
- 寫一手好SQL,該從哪裡入手最好?
- PHP命令列指令碼接收傳入引數的三種方式
- 使用 Shell 在多伺服器上批量操作
- PHP實現使用者異地登入提醒功能的方法