首頁  >  文章  >  後端開發  >  PHP7垃圾回收機制詳解(附GC處理完整流程圖)

PHP7垃圾回收機制詳解(附GC處理完整流程圖)

藏色散人
藏色散人轉載
2020-08-25 17:49:295014瀏覽

垃圾回收:

# 簡稱GC。顧名思義,就是廢物再利用的意思。
在說垃圾回收機制之前,先接觸一下記憶體洩漏。

推薦教學:《PHP7

記憶體洩漏:

某大神重口味充滿畫面感的形象解釋:

大概意思是申請了一塊地兒拉了會兒屎,拉完後不收拾,那麼那塊兒地就算是糟蹋了,地越用越少,最後一地全是屎。說到底一句,用了記得還。一定程度上說,垃圾回收機制就是用來擦屁股的。

c語言垃圾回收機制:

如果用過C語言,那麼申請記憶體的方式是malloc或是calloc,然後你用完這個記憶體後,一定不要忘了用free函數去釋放掉,這就是手動垃圾回收,通常都是大神用這種方式。

php的自動垃圾回收機制是怎麼樣的呢?

這個問題我們先這麼想,我們都知道php是C語言實現的。你想想如何用C語言實現對一個變數的統計以及釋放。 C語言是如何實現一個變量,從聲明開始到最後沒人用了,就把這個變量所佔的內存給釋放掉(被垃圾回收)。

PHP進行記憶體管理的核心演算法一共兩項:
一是引用計數,二是寫時拷貝

聲明一個PHP變數的時候,C語言在底層產生一個叫做zval的struct(結構體),如下:

zval {
string "a" //变量的名字是a
value zend_value //变量的值,联合体
type string //变量是字符串类型
}

zval struct結構體

(1)保存php $a的變數名稱
(2) php $a的變數類型
(3)php變數$a的zend_value聯合體

如果給變數賦值了,例如“hello world”,那麼C語言就在底層再產生一個叫做zend_value的union(聯合體)

zend_value {
string "hello world" //值的内容
refcount 1 //引用计数
}

zend_value的union(聯合體)

(1)保存php $a的變數的值hello world
(2)記錄php $a變數引用次數

看到zval struct結構體和zend_value,如果面試官問你php變數為什麼能夠保存字串"123"也能保存數字123,你知道該怎麼回答了吧?就答出重點zval中有該變數的型,當是字串123的時候,type就是string,此時value指向「123」;當是整數123的時候,zval的type為int,value為123。

何為引用計數?

程式碼實戰解析php變數參考計數

$a = 'hello,world';
echo xdebug_debug_zval( 'a');//refcount=1
$b = $a;
echo xdebug_debug_zval( 'a'); //$b引用$a,故变量a,refcount=2
$c = $a;
echo xdebug_debug_zval( 'a'); //$c引用$a,故变量a,refcount=3
unset( $c );
echo xdebug_debug_zval( 'a');//删除了$c的引用,故变量a,refcount=2

運行結果:

a:
(refcount=1, is_ref=0)string 'hello, world' (length=11)
a:
(refcount=2, is_ref=0)string 'hello,world' (length=11)
a:
(refcount=3, is_ref= 0)string 'hello,world' (length=11)
a:
(refcount=2, is_ref=0)string 'hello,world' (length=11)

#何為拷貝複製?

$a = 'hello';
$b = $a;//$a赋值给$b的时候,$a的值并没有真的复制了一份
echo xdebug_debug_zval( 'a');//$a的引用计数为2
$a = 'world';//当我们修改$a的值为123的时候,这个时候就不得已进行复制,避免$b的值和$a的一样
echo xdebug_debug_zval( 'a');///$a的引用计数为1

執行結果:

a:
(refcount=2, is_ref=0)string 'hello' (length=5)
a:
(refcount=1, is_ref=0)string 'world' (length=5)

其實,當你把$a賦值給$b的時候,$a的值並沒有真的複製了一份,這樣是對內存的極度不尊重,也是對時間複雜度的極度不尊重,計算機僅僅是將$b指向了$a的值而已,這就叫多快好省。那麼,什麼時候真正的發生複製呢?就是當我們修改$a的值為123的時候,這個時候就不得已進行複製,避免$b的值和$a的一樣。

透過簡單的案例解釋清楚了兩個要點:引用計數和寫入時拷貝。

垃圾回收機制:

當一個zval在被unset的時候、或是從一個函數中運行完畢出來(就是局部變數)的時候等等很多地方,都會產生zval與zend_value發生斷開的行為,這時候zend引擎需要偵測的就是zend_value的refcount是否為0,如果為0,則直接KO free空出內容來。如果zend_value的recount不為0,這個value不能被釋放,但是也不代表這個zend_value是清白的,因為此zend_value依然可能是個垃圾。

(1)當php變數$a的refcount=0時,變數$a就會被垃圾回收
(2)當php變數$a的refcount>0時,變數$ a但也可能被認為是垃圾

什麼樣的情況會導致zend_value的refcount不為0,但是這個zend_value卻是個垃圾呢?

$arr = [ 1 ];
$arr[] = &$arr;
unset( $arr );

这种情况下,zend_value不会能释放,但也不能放过它,不然一定会产生内存泄漏,所以这会儿zend_value会被扔到一个叫做垃圾回收堆中,然后zend引擎会依次对垃圾回收堆中的这些zend_value进行二次检测,检测是不是由于上述两种情况造成的refcount为1但是自身却确实没有人再用了,如果一旦确定是上述两种情况造成的,那么就会将zend_value彻底抹掉释放内存。

垃圾回收发生在什么时候?

有些同学可能有疑问,就是php不是运行一次就销毁了吗,我要gc有何用?并不是的,首先当一次fpm运行完毕后,最后一定还有gc的,这个销毁就是gc;其次是,内存都是即用即释放的,而不是攒着非得到最后,你想想一个典型的场景,你的控制器里的某个方法里用了一个函数,函数需要一个巨大的数组参数,然后函数还需要修改这个巨大的数组参数,你们应该是函数的运行范围里面修改这个数组,所以此时会发生写时拷贝了,当函数运行完毕后,就得赶紧释放掉这块儿内存以供给其他进程使用,而不是非得等到本地fpm request彻底完成后才销毁。

(1)fpm运行完毕后,最后一定会gc的
(2)运行过程中,也会gc的,内存都是即用即释放的,而不是攒着非得到最后gc

GC处理完整流程图

 

以上是PHP7垃圾回收機制詳解(附GC處理完整流程圖)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除