# 新的GC機制
## 參考
[引用計數基本知識](https://www.php.net/manual/zh/features.gc.refcounting-basics.php)
[PHP的垃圾回收機制](https://www.cnblogs.com/xuxubaobao/p/10840176.html)
[# PHP內核探索之變量---變量的容器-Zval](https://blog.csdn.net/ohmygirl/article/details/41542445)
[新的垃圾回收機制](https://blog.csdn.net/phpkernel/article/details/5734743)
## 5.3之前的內存釋放
在5.2及更早版本的PHP中,沒有專門的垃圾回收器GC(Garbage Collection),引擎在判斷一個變量空間是否能夠被釋放的時候是依據這個變量的zval的refcount的值,如果refcount為0,那么變量的空間可以被釋放,否則就不釋放,這是一種非常簡單的GC實現。
在上一章節中,我們了解到了如果在復雜的類型中使用引用符號不會被上述原理所檢測到,從而會造成內存泄漏,所以在5.3之后的PHP版本中,官方引入了一種簽全新的GC機制。
## 新的GC
在PHP5.3版本中,使用了專門GC機制清理垃圾,在之前的版本中是沒有專門的GC,那么垃圾產生的時候,沒有辦法清理,內存就白白浪費掉了。在PHP5.3源代碼中多了以下文件:{PHPSRC}/Zend/zend\_gc.h {PHPSRC}/Zend/zend\_gc.c, 這里就是新的GC的實現,我們先簡單的介紹一下算法思路,然后再從源碼的角度詳細介紹引擎中如何實現這個算法的。
**新的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就是一個垃圾。這個原理咋看起來很簡單,但是又不是那么容易理解,起初筆者也無法理解其含義,直到挖掘了源代碼之后才算是了解。如果你現在不理解沒有關系,后面會詳細介紹,這里先把這算法的幾個步驟描敘一下,首先引用手冊中的一張圖:

A:為了避免每次變量的refcount減少的時候都調用GC的算法進行垃圾判斷,此算法會先把所有前面準則3情況下的zval節點放入一個節點(root)緩沖區(root buffer),并且將這些zval節點標記成紫色,同時算法必須確保每一個zval節點在緩沖區中之出現一次。當緩沖區被節點塞滿的時候,GC才開始開始對緩沖區中的zval節點進行垃圾判斷。
B:當緩沖區滿了之后,算法以深度優先對每一個節點所包含的zval進行減1操作,為了確保不會對同一個zval的refcount重復執行減1操作,一旦zval的refcount減1之后會將zval標記成灰色。需要強調的是,這個步驟中,起初節點zval本身不做減1操作,但是如果節點zval中包含的zval又指向了節點zval(環形引用),那么這個時候需要對節點zval進行減1操作。
C:算法再次以深度優先判斷每一個節點包含的zval的值,如果zval的refcount等于0,那么將其標記成白色(代表垃圾),如果zval的refcount大于0,那么將對此zval以及其包含的zval進行refcount加1操作,這個是對非垃圾的還原操作,同時將這些zval的顏色變成黑色(zval的默認顏色屬性)
D:遍歷zval節點,將C中標記成白色的節點zval釋放掉。
對于一個包含環形引用的數組,對數組中包含的每個元素的zval進行減1操作,之后如果發現數組自身的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開啟的時候,垃圾分析算法將在節點緩沖區(roots buffer)滿了之后啟動。緩沖區默認可以放10,000個節點,當然你也可以通過修改Zend/zend\_gc.c中的*GC\_ROOT\_BUFFER\_MAX\_ENTRIES?*來改變這個數值,需要重新編譯鏈接PHP。當GC關閉的時候,垃圾分析算法就不會運行,但是相關節點還會被放入節點緩沖區,這個時候如果緩沖區節點已經放滿,那么新的節點就不會被記錄下來,這些沒有被記錄下來的節點就永遠也不會被垃圾分析算法分析。如果這些節點中有循環引用,那么有可能產生內存泄漏。之所以在GC關閉的時候還要記錄這些節點,是因為簡單的記錄這些節點比在每次產生節點的時候判斷GC是否開啟更快,另外GC是可以在腳本運行中開啟的,所以記錄下這些節點,在代碼運行的某個時候如果又開啟了GC,這些節點就能被分析算法分析。當然垃圾分析算法是一個比較耗時的操作。
??? 在PHP代碼中我們可以通過gc\_enable()和gc\_disable()函數來開啟和關閉GC,也可以通過調用gc\_collect\_cycles()在節點緩沖區未滿的情況下強制執行垃圾分析算法。這樣用戶就可以在程序的某些部分關閉或則開啟GC,也可強制進行垃圾分析算法。