GC

簡介:PHP 是一門托管型語言敬锐,在 PHP 編程中,程序員不需要手工處理內存資源的分配與釋放(使用 C 編寫 PHP 或 Zend 擴展除外),這就意味著 PHP 本身實現(xiàn)了垃圾回收機制(Garbage Collection)间雀。在?PHP 官方網站可以看到對垃圾回收機制的介紹催什,在開始介紹垃圾回收之前我們先介紹幾個關鍵的概念涵亏。


PHP的引用計數:(refcount洞难,is_ref)

PHP在內核中是通過zval這個結構體來存儲變量的鸵熟,在Zend/zend.h文件中找到了其定義:

PHP5 中定義如下:

struct_zval_struct {

zvalue_valuevalue;

zend_uint refcount;? ? ? ??

zend_uchar type;/* active type */

zend_uchar is_ref;

};


一個例子:

第一步:查看內部結構

$a = 'zd';??

我們可以使用xdebug查看zval的信息

xdebug_debug_zval('a');?

存儲的內部信息是 name:(refcount=1, is_ref=0)='zd'?

說明php通過zavl的refcount變量來存儲引用計數

第二步:增加一個計數

$a = 'zd';? ?$b = $a;

xdebug_debug_zval('a');?

存儲的內部信息是:?name:(refcount=2, is_ref=0),string'zd'?

證明這種情況下并不會開辟兩塊內存空間骇径,并且refcount會加1

第三步驟:引用賦值

當我們使用值引用時豌注,$a = 'zd';? ?$b = &$a;

xdebug_debug_zval('a');?

存儲的內部信息是:name:(refcount=2, is_ref=1)='zd'

所以我們可以的得到的信息是:引用賦值會導致zval通過is_ref來標記是否存在引用的情況夫偶。

第四步:數組型的變量

$test = ['a'=>'1','b'=>'2'];?

xdebug_debug_zval('test');

test: (refcount=1, is_ref=0)=array (?

? ?'a' => (refcount=1, is_ref=0)='1',?

? ? 'b' => (refcount=1,is_ref=0)='2'?

)

由此我們可以明白:數組內部的元素也會生成對應的zavl纤泵,上述的例子存在3個zval,這3個zval都遵循變量的引用和計數原則只恨。

第五步:unset掉變量运怖,使refcount減1

$a = 'zd';

unset($a);

xdebug_debug_zval('a');

a:(refcount=0, is_ref=0)='zd'??


上面提到的refcount=0的情況灵巧,會被自動的銷毀掉搀矫,其實并不屬于垃圾,下面我們來想一想為什么是垃圾刻肄?為什么要有垃圾回收呢瓤球,如果不回收會引入什么問題呢?

PHP5.3 之前的內存泄漏的垃圾回收

產生內存泄漏主要真兇:環(huán)形引用敏弃。 現(xiàn)在來造一個環(huán)形引用的場景:

$a= ['one'];

$a[] = &$a;

xdebug_debug_zval('a');

a: (refcount=2, is_ref=1)=array (

????????0 => (refcount=1, is_ref=0)='one',

????????1 => (refcount=2, is_ref=1)=…

)

“…”表示1指向a自身卦羡,是一個環(huán)形引用

環(huán)形引用

這個時候我們對$a進行unset,那么$a會從符號表中刪除,同時$a指向的zval的refcount減少1麦到,那么問題也就產生了绿饵,$a已經不在符號表中了,用戶無法再訪問此變量瓶颠,但是$a之前指向的zval的refcount由2變?yōu)?而不是0拟赊,因此不能被回收,這樣產生了內存泄露:


為解決這種垃圾粹淋,產生了新的GC

? ? ? ??在較新的PHP手冊中有簡單的介紹新的GC使用的垃圾清理算法吸祟,這個算法名為?Concurrent Cycle Collection in Reference Counted Systems?, 這里不詳細介紹此算法桃移,根據手冊中的內容來先簡單的介紹一下思路:

首先我們有幾個基本的準則:

1:如果一個zval的refcount增加欢搜,那么此zval還在使用,不屬于垃圾

2:如果一個zval的refcount減少到0谴轮,?那么zval可以被釋放掉炒瘟,不屬于垃圾

3:如果一個zval的refcount減少之后大于0,那么此zval還不能被釋放第步,此zval可能成為一個垃圾

只有在準則3下疮装,GC才會把zval收集起來缘琅,然后通過新的算法來判斷此zval是否為垃圾。那么如何判斷這么一個變量是否為真正的垃圾呢廓推?簡單的說刷袍,就是對此zval中的每個元素進行一次refcount減1操作,操作完成之后樊展,如果zval的refcount=0呻纹,那么這個zval就是一個垃圾。這個原理咋看起來很簡單专缠,但是又不是那么容易理解雷酪,起初筆者也無法理解其含義,直到挖掘了源代碼之后才算是了解涝婉。如果你現(xiàn)在不理解沒有關系哥力,后面會詳細介紹,這里先把這算法的幾個步驟描敘一下,首先引用手冊中的一張圖:


垃圾回收的過程

A:為了避免每次變量的refcount減少的時候都調用GC的算法進行垃圾判斷墩弯,此算法會先把所有前面準則3情況下的zval節(jié)點放入一個節(jié)點(root)緩沖區(qū)(root buffer)吩跋,并且將這些zval節(jié)點標記成紫色,同時算法必須確保每一個zval節(jié)點在緩沖區(qū)中之出現(xiàn)一次渔工。當緩沖區(qū)被節(jié)點塞滿的時候锌钮,GC才開始開始對緩沖區(qū)中的zval節(jié)點進行垃圾判斷。

B:當緩沖區(qū)滿了之后引矩,算法以深度優(yōu)先對每一個節(jié)點所包含的zval進行減1操作轧粟,為了確保不會對同一個zval的refcount重復執(zhí)行減1操作,一旦zval的refcount減1之后會將zval標記成灰色脓魏。需要強調的是,這個步驟中通惫,起初節(jié)點zval本身不做減1操作茂翔,但是如果節(jié)點zval中包含的zval又指向了節(jié)點zval(環(huán)形引用),那么這個時候需要對節(jié)點zval進行減1操作履腋。

C:算法再次以深度優(yōu)先判斷每一個節(jié)點包含的zval的值珊燎,如果zval的refcount等于0,那么將其標記成白色(代表垃圾)遵湖,如果zval的refcount大于0悔政,那么將對此zval以及其包含的zval進行refcount加1操作,這個是對非垃圾的還原操作延旧,同時將這些zval的顏色變成黑色(zval的默認顏色屬性)

D:遍歷zval節(jié)點谋国,將C中標記成白色的節(jié)點zval釋放掉。

這ABCD四個過程是手冊中對這個算法的介紹迁沫,這還不是那么容易理解其中的原理芦瘾,這個算法到底是個什么意思呢捌蚊?我自己的理解是這樣的:

????????比如還是前面那個變成垃圾的數組$a對應的zval,命名為zval_a,? 如果沒有執(zhí)行unset,?zval_a的refcount為2,分別由$a和$a中的索引1指向這個zval近弟。??

????????用算法對這個數組中的所有元素(索引0和索引1)的zval的refcount進行減1操作缅糟,由于索引1對應的就是zval_a,所以這個時候zval_a的refcount應該變成了1祷愉,這樣zval_a就不是一個垃圾窗宦。

????????如果執(zhí)行了unset操作,zval_a的refcount就是1二鳄,由zval_a中的索引1指向zval_a,用算法對數組中的所有元素(索引0和索引1)的zval的refcount進行減1操作赴涵,這樣zval_a的refcount就會變成0,于是就發(fā)現(xiàn)zval_a是一個垃圾了泥从。 算法就這樣發(fā)現(xiàn)了頑固的垃圾數據句占。

舉了這個例子,讀者大概應該能夠知道其中的端倪:

對于一個包含環(huán)形引用的數組躯嫉,對數組中包含的每個元素的zval進行減1操作纱烘,之后如果發(fā)現(xiàn)數組自身的zval的refcount變成了0,那么可以判斷這個數組是一個垃圾祈餐。

這個道理其實很簡單擂啥,假設數組a的refcount等于m, a中有n個元素又指向a,如果m等于n,那么算法的結果是m減n,m-n=0帆阳,那么a就是垃圾哺壶,如果m>n,那么算法的結果m-n>0,所以a就不是垃圾了

m=n代表什么?? 代表a的refcount都來自數組a自身包含的zval元素,代表a之外沒有任何變量指向它蜒谤,代表用戶代碼空間中無法再訪問到a所對應的zval山宾,代表a是泄漏的內存,因此GC將a這個垃圾回收了鳍徽。


PHP中運用新的GC的算法

在PHP中资锰,GC默認是開啟的,你可以通過ini文件中的zend.enable_gc 項來開啟或則關閉GC阶祭。當GC開啟的時候绷杜,垃圾分析算法將在節(jié)點緩沖區(qū)(roots buffer)滿了之后啟動。緩沖區(qū)默認可以放10,000個節(jié)點濒募,當然你也可以通過修改Zend/zend_gc.c中的GC_ROOT_BUFFER_MAX_ENTRIES?來改變這個數值鞭盟,需要重新編譯鏈接PHP。

當GC關閉的時候瑰剃,垃圾分析算法就不會運行齿诉,但是相關節(jié)點還會被放入節(jié)點緩沖區(qū),這個時候如果緩沖區(qū)節(jié)點已經放滿,那么新的節(jié)點就不會被記錄下來鹃两,這些沒有被記錄下來的節(jié)點就永遠也不會被垃圾分析算法分析遗座。如果這些節(jié)點中有循環(huán)引用,那么有可能產生內存泄漏俊扳。

之所以在GC關閉的時候還要記錄這些節(jié)點途蒋,是因為簡單的記錄這些節(jié)點比在每次產生節(jié)點的時候判斷GC是否開啟更快,另外GC是可以在腳本運行中開啟的馋记,所以記錄下這些節(jié)點号坡,在代碼運行的某個時候如果又開啟了GC,這些節(jié)點就能被分析算法分析梯醒。當然垃圾分析算法是一個比較耗時的操作宽堆。

??? 在PHP代碼中我們可以通過gc_enable()和gc_disable()函數來開啟和關閉GC,也可以通過調用gc_collect_cycles()在節(jié)點緩沖區(qū)未滿的情況下強制執(zhí)行垃圾分析算法茸习。這樣用戶就可以在程序的某些部分關閉或則開啟GC畜隶,也可強制進行垃圾分析算法。


1.unset函數

????unset只是斷開一個變量到一塊內存區(qū)域的連接号胚,同時將該內存區(qū)域的引用計數-1籽慢;內存是否回收主要還是看refount是否到0了,以及gc算法判斷猫胁。

2.= null 操作箱亿;

????a=null是直接將a 指向的數據結構置空,同時將其引用計數歸0弃秆。

3.腳本執(zhí)行結束

????腳本執(zhí)行結束届惋,該腳本中使用的所有內存都會被釋放,不論是否有引用環(huán)菠赚。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末脑豹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子衡查,更是在濱河造成了極大的恐慌瘩欺,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件峡捡,死亡現(xiàn)場離奇詭異,居然都是意外死亡筑悴,警方通過查閱死者的電腦和手機们拙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阁吝,“玉大人砚婆,你說我怎么就攤上這事。” “怎么了装盯?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵坷虑,是天一觀的道長。 經常有香客問我埂奈,道長迄损,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任账磺,我火速辦了婚禮芹敌,結果婚禮上,老公的妹妹穿的比我還像新娘垮抗。我一直安慰自己氏捞,他們只是感情好,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布冒版。 她就那樣靜靜地躺著液茎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辞嗡。 梳的紋絲不亂的頭發(fā)上捆等,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音欲间,去河邊找鬼楚里。 笑死,一個胖子當著我的面吹牛猎贴,可吹牛的內容都是我干的班缎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼她渴,長吁一口氣:“原來是場噩夢啊……” “哼达址!你這毒婦竟也來了?” 一聲冷哼從身側響起趁耗,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤沉唠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后苛败,有當地人在樹林里發(fā)現(xiàn)了一具尸體满葛,經...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年罢屈,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘀韧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡缠捌,死狀恐怖锄贷,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤谊却,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布柔昼,位于F島的核電站,受9級特大地震影響炎辨,放射性物質發(fā)生泄漏捕透。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一蹦魔、第九天 我趴在偏房一處隱蔽的房頂上張望激率。 院中可真熱鬧,春花似錦勿决、人聲如沸乒躺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嘉冒。三九已至,卻和暖如春咆繁,著一層夾襖步出監(jiān)牢的瞬間讳推,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工玩般, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留银觅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓坏为,卻偏偏與公主長得像究驴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子匀伏,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內容

  • 每一種語言都有自己的自動垃圾回收機制洒忧,讓程序員不必過分關心程序內存分配,但是在OOP中够颠,有些對象需要顯式的銷毀熙侍;防...
    文檔隨手記閱讀 4,737評論 2 3
  • 這篇文章是我之前翻閱了不少的書籍以及從網絡上收集的一些資料的整理,因此不免有一些不準確的地方履磨,同時不同JDK版本的...
    高廣超閱讀 15,599評論 3 83
  • 原文閱讀 前言 這段時間懈怠了蛉抓,罪過! 最近看到有同事也開始用上了微信公眾號寫博客了剃诅,挺好的~給他們點贊巷送,這博客我...
    碼農戲碼閱讀 5,962評論 2 31
  • G1 GC知識點: Region:1M~64M,2的冪综苔,默認為其大小為將堆分為約2048個region為宜惩系。可以通...
    陽丶小光閱讀 4,845評論 0 7
  • JVM內存區(qū)域 了解Java GC之前如筛,必須先搞清楚JVM中內存區(qū)域的劃分堡牡。 JVM中內存區(qū)域大致可分為如上圖所示...
    特立獨行的豬手閱讀 2,632評論 0 13