PHP的垃圾回收機制

垃圾回收,簡稱gc。顧名思義,就是廢物重利用的意思。再說這個以前先接觸一下內存泄露,大概意思就是申請了一塊地兒拉了會兒屎,拉完後不收拾,那麼那塊兒地就算是糟蹋了,地越用越少,最後一地全是屎。說到底一句,用了記得還。必定程度上說,垃圾回收機制就是用來擦屁股的。若是用過C語言,那麼申請內存的方式是malloc或者是calloc,而後你用完這個內存後,必定不要忘了用free函數去釋放掉,這就是傳說中手動垃圾回收,通常都是掃地神僧用這種方式。不少高層次語言中,你這輩子都是接觸不到內存管理的,好比世界上最好的語言php,這種語言替你管理了內存,你就安安心心寫爛代碼便可。寫php的,你說你關心內存,我是不怎麼相信的,必定是你在裝逼。固然了,若是你用的swoole或者wm或者本身發明的常駐內存級php應用,那你將不得不關注內存泄露問題,也就說必定要記得釋放無用變量。那麼,在用的最廣泛地最傳統的web開發中,php的自動垃圾回收機制是怎樣的呢?這個問題咱們先這麼想,就是都知道php是C語言實現的,如今把C語言給你放在這裏了,而後你想一想如何用C語言實現對一個變量的統計以及釋放。你不要想如何實現php,你就想C語言如何實現一個變量,從聲明開始到最後沒人用了,就把這個變量所佔的內存給釋放掉。你從這個角度出發,就會舒服一些,這再也不是一個技術難題,而是一個傻逼產品經理提的一個傻逼需求。好了,步入正題,PHP進行內存管理的核心算法一共兩項:一是引用計數,二是寫時拷貝,請理(bei)解(song)。當你聲明一個PHP變量的時候,C語言就在底層給你搞了一個叫作zval的struct(結構體);若是你還給這個變量賦值了,好比「hello world」,那麼C語言就在底層再給你搞一個叫作zend_value的union(聯合體),整體看來就是這樣的:php

好了,進入代碼實戰階段,注意兩點:web

$a = 'hello'. mt_rand( 1, 1000 );面試

 

echo xdebug_debug_zval( 'a');算法

$b = $a;數組

echo xdebug_debug_zval( 'a');swoole

$c = $a;函數

echo xdebug_debug_zval( 'a');學習

unset( $c );debug

echo xdebug_debug_zval( 'a');設計

輸出的結果是:

其中,zval struct結構體用於保存$a,zend_value union聯合體用於保存數據內容也就是'hello916'。因爲後面又聲明瞭b和c,因此C不得不又在底層給你搞出兩個zval struct結構體來。

其中,zval和zend value的結構大概以下:(注意!!!這並非完整正確的PHP zval和zend_value在C語言中struct和union實現,僅僅是挑出最重點的部分寫出來,強調一下:你沒有必要一個字不差背誦過zval和zend_value,你只須要知道原理)

zval {

string "a" //變量的名字是a

value zend_value //變量的值

type string //變量是字符串類型

}

zend_value {

string "hello916" //值的內容

refcount 1 //引用計數

}

看到上面兩個,若是面試官問你php變量爲何可以保存字符串"123"也能保存數字123,你知道該怎麼回答了吧?就答出重點zval中有該變量的類型,當是字符串123的時候,type就是string,此時value指向「123」;當是整數123的時候,zval的type爲int,value爲123。這就是答題的思想,這很重要!並且,經過C語言都是能夠實現的!具體真正的val和zend_value的模樣,有興趣的同窗能夠去網上搜搜,若是你沒有C語言的底子,可能比較吃力!前者是一個struct結構體,後者是一個union聯合體!

這個refcount就是傳說中的引用計數了,初始化的時候a後面的引用次數爲1(注意,正確說法應該是a後面的賦值的數組zend_value引用計數爲1,而不是a這個變量zval自己)。而後咱們將$b = $a,其實至關於又一個變量指向了這個zend_value,因此refcount變爲2,最後將$c = $a,同理,zend_value的refcount再次加1變成了3。而後,咱們用unset( $c ),這會兒,C語言要作的就是把$c的zval給KO free掉,可是並非free zend_value,這會兒zend_value的refcount就天然而然減1變成2了。

那麼寫時拷貝是什麼意思呢?看下面代碼:

<?php

// 先不要問爲何非要加mt_rand,否則,絕筆說不過來了,處處都是坑

$a = 'hello'. mt_rand( 1, 1000 );

$b = $a;

$a = 123;

echo $b. PHP_EOL;

// 運行結果,不用我說吧,腳趾頭都知道是'hello'.mt_rand( 1, 1000 )的結果,絕對不多是123。

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

<?php

$a = 'hello'. mt_rand( 1, 1000 );

$b = $a;

echo xdebug_debug_zval( 'a');

$a = 'world'. mt_rand( 2, 2000 );

echo xdebug_debug_zval( 'a');

// 運行結果爲1,其中的原理你本身應該能理順了昂

叨逼叨了這麼長,經過簡單的案例解釋清楚了兩個要點:引用計數和寫時拷貝,那麼垃圾回收也該來了。當一個zval在被unset的時候、或者從一個函數中運行完畢出來(就是局部變量)的時候等等不少地方,都會產生zval與zend_value發生斷開的行爲,這個時候zend引擎須要檢測的就是zend_value的refcount是否爲0,若是爲0,則直接KO free空出內容來。若是zend_value的recount不爲0(廢話必定是大於0),這個value不能被釋放,可是也不表明這個zend_value是清白的,由於此zend_value依然多是個垃圾。

什麼樣的狀況會致使zend_value的refcount不爲0,可是這個zend_value倒是個垃圾呢?PHP7種兩種狀況:

<?php

$arr = [ 1 ];

$arr[] = &$arr;

unset( $arr );

這種狀況下,zend_value不會能釋放,但也不能放過它,否則必定會產生內存泄漏,因此這會兒zend_value會被扔到一個叫作垃圾回收堆中,而後zend引擎會依次對垃圾回收堆中的這些zend_value進行二次檢測,檢測是否是因爲上述兩種狀況形成的refcount爲1可是自身卻確實沒有人再用了,若是一旦肯定是上述兩種狀況形成的,那麼就會將zend_value完全抹掉釋放內存。

那麼垃圾回收發生在何時?有些同窗可能有疑問,就是php不是運行一次就銷燬了嗎,我要着gc有何用?並非啦,首先當一次fpm運行完畢後,最後必定還有gc的,這個銷燬就是gc;其次是,內存都是即用即釋放的,而不是攢着非獲得最後,你想一想一個典型的場景,你的控制器裏的某個方法裏用了一個函數,函數須要一個巨大的數組參數,而後函數還須要修改這個巨大的數組參數,大家應該是函數的運行範圍裏面修改這個數組,因此此時會發生寫時拷貝了,當函數運行完畢後,就得趕忙釋放掉這塊兒內存以供給其餘進程使用,而不是非得等到本地fpm request完全完成後才銷燬。

說到最後,說些本身的話:大多數狀況下,面試官問你問題主要是想一是要你個思惟思路,二是看你學習程度。就像gc這個問題,其實不少腳本語言的垃圾回收機制基本上都是靠引用計數和寫時拷貝這兩種算法結合完成的,因此若是你設計一門腳本語言,gc機制就按照這兩種算法進行設計便可。其次是大多數phper不會看這些東西的,面試官問你這個問題不是要你死記硬背那麼多細節,你背不過的,他仍是想探測你平時有沒有更積極地往深層發展的心態。

注重體現重點,不少細節實在無法寫,好比我舉個例子$a=[],xdebug_debug_zval( $a )的refcount值你猜是多少? 7.1.17下居然是2,你是否是覺得是1,然而並非。不過你不用糾結這些細節,gc的關鍵就是能說出引用計數的原理和寫時拷貝,不少細節深處都各類奇奇怪怪的東西,面試官本身都不必定知道。

相關文章
相關標籤/搜索