baiyanphp
所有視頻:https://segmentfault.com/a/11...算法
原視頻地址:http://replay.xesv5.com/ll/26...segmentfault
因爲這個系列的視頻後面會再次細講垃圾回收,那麼咱們今天先複習一下PHP中的引用,爲後面作一個鋪墊,後續的筆記會詳細講解垃圾回收器的相關運行原理。數組
PHP7中的引用
- 引用:能夠經過不一樣的變量名,訪問同一個變量內容。
- PHP7中的引用經過讓兩個變量指向同一塊內存空間實現了上述特性。在進行引用賦值後,等號左右兩邊的變量均變成了引用類型(IS_REFERENCE)。這塊公用的內存空間就是PHP7爲引用類型的變量專門建立的一個結構體,叫作zend_reference。
- 代碼示例:
$a = 1;
echo $a;
$b = &$a; //$b是$a的引用
echo $a;
echo $b;
unset($b);
echo $a;
- 咱們用gdb調試以上代碼:
- 首先執行$a = 1;而且打印$a的值,$a就是一個普通的zval,其類型是IS_LONG,很好理解:
![](http://static.javashuo.com/static/loading.gif)
- 執行關鍵的一步:$b = &$a,打印$a的值,觀察$a的存儲狀況:
![](http://static.javashuo.com/static/loading.gif)
- 觀察上圖,能夠發現$a的type變成了10 (IS_REFERENCE)類型,而且ref字段指向了一個新的結構體,這就是zend_reference,zend_reference中存儲着$a與$b共同的值1,因爲$a與$b同時引用着這個結構體,故此時該結構體的refcount = 2。
- 接下來打印$b,觀察$b的存儲狀況:
![](http://static.javashuo.com/static/loading.gif)
- 觀察上圖,發現與$b的type也是IS_REFERENCE類型,且ref字段也指向了一個zend_reference結構體,比較$a與$b指向的zend_reference,兩者地址相同,說明指向了同一個zend_reference結構體。此時兩個變量的存儲狀況以下圖所示:
![](http://static.javashuo.com/static/loading.gif)
- 接下來執行unset($b),觀察$a以及zend_reference的存儲狀況,咱們看是否符合預期:
![](http://static.javashuo.com/static/loading.gif)
- 咱們看到unset($b)以後,$a所指向的zend_reference的refcount由2變爲1,說明如今只有$a引用着這個結構體,b再也不引用這個結構體,其類型變成了IS_UNDEF類型,代碼執行完畢。
- 那麼咱們看一下zend_reference結構體的基本結構:
struct _zend_reference {
zend_refcounted_h gc; //gc相關,存有refcount
zval val; //引用類型的變量值存在這個zval中的zend_value字段中。簡單類型的值直接存在這裏,複雜類型的值存儲對應數據結構的指針,來找到這個變量的值,和以前講基本變量時候講過的同樣。
};
- 這個結構體一共只有2個字段,gc字段中是zend_refcounted_gc結構體類型,其中存儲了引用計數;val字段存儲了引用類型變量的值(簡單類型如整型、浮點型的值直接存在這裏,複雜類型存對應數據結構的指針,與以前講基本變量的時候講過的同樣)。這樣至關於加了一箇中間層,使得原始的zend_string或zend_array在內存中只有1份,方便管理與維護。
循環引用問題
<?php
$a = ['time' => time()];
echo $a;
$a[] = &$a; //循環引用
echo $a;
unset($a);
echo $a;
- 注意:因爲開啓opcache的PHP7會在數組初始化的元素所有爲常量元素的時候,將其優化成不可變數組(immutable array),這裏的引用計數值refcount = 2只是一個僞引用計數,因此咱們使用$a = ['time' => time()],讓其初始化後的refcount爲正常的1。]見下圖:
![](http://static.javashuo.com/static/loading.gif)
- 利用gdb調試這段代碼:
- 執行完$a初始化並打印$a,refcount爲1,type爲7(IS_ARRAY)而此時的ref字段中的值是非法地址,說明此時尚未生成中間的zend_reference結構體:
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
-
繼續執行下一行$a[] = &$a; 觀察下圖中綠色方框的含義:數據結構
- $a的zval中的ref指向zend_reference結構體
- zend_reference結構體中的zval字段中的arr指針指向了原始的zend_array
- zend_array中的arData指針指向了bucket類型
- zend_array中的bucket數組元素也是一個IS_REFERENCE類型,它又指回到同一個zend_reference結構體:
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
- 因爲有兩個東西指向zend_reference結構體(一個是$a,一個是$a數組中的一個元素),因此refcount = 2。原始的zend_array中也有一個refcount字段,因爲只有一個zend_reference指向這個zend_array,因此refcount = 1。
- 接下來繼續執行unset($a):
![](http://static.javashuo.com/static/loading.gif)
- 咱們能夠看到,$a的type類型變成了0(IS_UNDEF),同時其指向的zend_reference結構體的refcount變爲了1(由於$a數組中的元素仍然在指向它),咱們畫圖來表示一下如今的內存狀況:
![](http://static.javashuo.com/static/loading.gif)
- 那麼問題出現了,$a是unset掉了,可是因爲原始的zend_array中的元素仍然在指向仍然在指向zend_reference結構體,因此zend_reference的refcount是1,而並不是是預期的0。這樣一來,這兩個zend_reference與zend_array結構在unset($a)以後,仍然存在於內存之中,若是對此不做任何處理,就會形成內存泄漏。
- 那麼如何解決循環引用帶來的內存泄漏問題呢?垃圾回收就要派上用場了。在PHP7中,若是檢測到refcount -1 後仍 > 0的變量,會把它放入一個雙向鏈表中,等待垃圾回收,至關於一個緩衝區的做用。待緩衝區滿了以後(10000個存儲單元),而後再對其進行標記和清除(之後會在代碼層面具體講垃圾回收的方法)。
- 緩衝區的做用就是減小垃圾回收算法運行的頻率,減小對正在運行的服務端代碼的影響。