大話PHP的垃圾回收機制
-
PHP可以自動進行記憶體管理,清除不需要的物件,主要使用了引用計數
-
在
zval
結構體中定義了ref_count
和is_ref
,ref_count
是引用計數 ,標識此zval
被多少個變數引用 , 為0時會被銷燬
is_ref
標識是否使用的&
取地址符強制引用 -
為了解決迴圈引用記憶體洩露問題 , 使用同步週期回收演算法
比如當陣列或物件迴圈的引用自身 ,unset
掉陣列的時候 , 當refcount-1
後還大於0的 , 就會被當成疑似垃圾 , 會進行遍歷 ,並且模擬的刪除一次refcount-1
如果是0就刪除 ,如果不是0就恢復
頑固垃圾的產生過程
<?php
$a = "new string";
?>
程式碼中,$a
變數內部儲存資訊為
a: (refcount_gc=1, is_ref_gc=0)='new string'
當把
a
賦
值
給
另
外
一
個
變
量
的
時
候
,
a賦值給另外一個變數的時候,
a賦值給另外一個變量的時候,a對應的zval的refcount_gc
會加1
<?php
$a = "new string";
$b = $a;
?>
此時
a
和
a和
a和b變數對應的內部儲存資訊為,
a
和
a和
a,b: (refcount_gc=2, is_ref=0)='new string'
當用unset刪除$b變數時,“new string” 的refcount_gc會減1變成1。
<?php
$a = "new string"; //a: (refcount_gc=1, is_ref_gc=0)='new string'
$b = $a; //a,b: (refcount_gc=2, is_ref=0)='new string'
unset ($b); //a: (refcount_gc=1, is_ref=0)='new string'
?>
對於普通的變數來說,這一切很正常,但是在複合型別變數(陣列和物件)中,會發生比較有意思的事情:
<?php
$a = array('meaning' => 'life', 'number' => 42);
?>
$a
內部儲存資訊為:
a: (refcount=1, is_ref=0)=array (
'meaning' => (refcount=1, is_ref=0)='life',
'number' => (refcount=1, is_ref=0)=42
)
陣列變數本身($a)在引擎內部實際上是一個雜湊表,這張表中有兩個zval項 meaning和number,所以實際上那一行程式碼中一共生成了3個zval,這3個zval都遵循變數的引用和計數原則,用圖來表示:
下面在$a
中新增一個元素,並將現有的一個元素的值賦給新的元素:
<?php
$a = array('meaning' => 'life', 'number' => 42);
$a['name'] = $a['meaning'];
?>
那麼$a
的內部儲存為 , “life” 的ref_count變成2 , 42的ref_count是1:
a: (refcount=1, is_ref=0)=array (
'meaning' => (refcount=2, is_ref=0)='life',
'number' => (refcount=1, is_ref=0)=42,
'name' => (refcount=2, is_ref=0)='life'
)
如果將陣列的引用賦值給陣列中的一個元素,有意思的事情就會發生:
<?php
$a = array('one');
$a[] = &$a;
?>
這樣
a
數
組
就
有
兩
個
元
素
,
一
個
索
引
為
0
,
值
為
字
符
o
n
e
,
另
外
一
個
索
引
為
1
,
為
a陣列就有兩個元素,一個索引為0,值為字元one,另外一個索引為1,為
a數組就有兩個元素,一個索引為0,值為字符one,另外一個索引為1,為a自身的引用,內部儲存如下:
a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=…
)
array
這個zval
的ref_count
是2 , 是一個環形引用
這時對$a
進行unset
,那麼
a
會
從
符
號
表
中
刪
除
,
同
時
‘
a會從符號表中刪除,同時`
a會從符號表中刪除,同時‘a指向的
zval的
refcount_gc`減少1.
<?php
$a = array('one');
$a[] = &$a;
unset($a);
?>
那麼問題就產生了,
a
已
經
不
在
符
號
表
中
,
用
戶
無
法
再
訪
問
此
變
量
,
但
是
a已經不在符號表中,使用者無法再訪問此變數,但是
a已經不在符號表中,用戶無法再訪問此變量,但是a之前指向的zval的refcount_gc
變為1而不是0,因此不能被回收,從而產生記憶體洩露,新的GC
要做的工作就是清理此類垃圾。
為了解決迴圈引用記憶體洩露問題 , 使用同步週期回收演算法 , 這種ref_count
減1後還大於0的會被作為疑似垃圾
比如當陣列或物件迴圈的引用自身 , unset
掉陣列的時候 , 當refcount-1
後還大於0的 , 會進行遍歷 ,並且模擬的刪除一次refcount-1
如果是0就刪除 ,如果不是0就恢復。
以上內容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務程式碼寫多了沒有方向感,不知道該從那裡入手去提升,對此我整理了一些資料,包括但不限於:分散式架構、高可擴充套件、高效能、高併發、伺服器效能調優、TP6,laravel,Redis,Swoole、Swoft、Kafka、Mysql優化、shell指令碼、Docker、微服務、Nginx等多個知識點高階進階乾貨需要的可以免費分享給大家,需要戳這裡PHP進階架構師>>>實戰視訊、大廠面試文件免費獲取