PHP 的垃圾回收機制

Java 種的垃圾回收機制,你們確定都有所瞭解,好比如何肯定垃圾,有兩種算法,引用計數法和可達性分析算法。Java 中使用的是可達性分析算法,而 PHP 使用的引用計數算法。咱們都知道引用計數算法較難處理循環引用的問題,PHP 這波奇怪的操做可太秀了,那 PHP 的垃圾回收原理是怎麼樣的?php

1、PHP 中的引用計數

1.1 如何肯定垃圾

原理: 給對象添加一個引用計數器,每當有一個地方引用它,計數器的值就加一。每當有一個引用失效,計數器的值就減一。算法

  • 若是一個變量 value 的 refcount 減一以後等於 0,此 value 能夠被釋放掉,不屬於垃圾。垃圾回收器不會處理數組

  • 若是一個變量 value 的 refcount 減一以後仍是大於 0,此 value 被認爲不能被釋放掉,可能成爲一個垃圾。緩存

  • 垃圾回收器將可能的垃圾收集起來,等達到必定數量後開始啓動垃圾鑑定程序,把真正的垃圾釋放掉。函數

缺點: 須要維護引用計數器,有必定的消耗。且較難處理循環引用的問題。後面也會講到如何解決這個問題。測試

下面的例子說明引用計數的是如何變化的:優化

mark

1.2 PHP 中的變量知識

每一個 php 變量存在一個叫 zval 的變量容器中。一個 zval 變量容器,除了包含變量的類型和值,還包括兩個字節的額外信息。3d

第一個是 is_ref,是個 bool 值,用來標識這個變量是不是屬於引用集合(reference set) 。經過這個字節,php 引擎才能把普通變量和引用變量區分開來,因爲 php 容許用戶經過使用&來使用自定義引用,zval 變量容器中還有一個內部引用計數機制,來優化內存使用。code

第二個額外字節是 refcount,用以表示指向這個 zval 變量容器的變量(也稱符號即 symbol )個數。對象

1.3 使用引用計數的類型

有 5 種類型用的引用計數:

string、array、object、resource、reference

下面的表格說明了只有 type_flag 爲如下 8 種類型且 IS_TYPE_REFOUNTED=true 的變量才使用引用計數,以下表所示

使用引用計數的類型

2、回收原理

2.1. 回收時機

  • 自動回收:在變量 zval 斷開 value 的指向時,若是發現 refcount=0 則會直接釋放 value。

    • 斷開 value 指向的情形
      • 修改變量時會斷開原有 value 的指向
      • 函數返回時會釋放全部的局部變量
  • 主動回收

    調用 unset() 函數。相似於 Java 中的 System.gc()

2.2 垃圾鑑定

垃圾收集器收集的可能垃圾到達必定數量後,啓動垃圾鑑定、回收程序。

原理:垃圾是因爲成員引用自身致使的,那麼就對 value 的 refcount 減一操做,若是 value 的 refount 變爲了 0,則代表其引用所有來自自身成員,value 屬於垃圾。另外垃圾只會出如今array、object類型中。

回收步驟

  • 步驟一: 遍歷垃圾回收器的 buffer 緩衝區,把 value 標爲灰色,把 value 的成員的 refount-1,仍是標爲灰色。

  • 步驟二: 遍歷垃圾回收器的 buffer 緩衝區,若是 value 的 refcount 等於 0,標爲白色,認爲是垃圾;若是不等於 0,則表示還有外部的引用,不是垃圾,將 refcount+1 還原回去,標爲黑色。

  • 步驟三: 遍歷垃圾回收器的 buffer 緩衝區,將 value 爲非白色的節點從 buffer 中刪除,最終 buffer 緩衝區中都是真正的垃圾。

  • 步驟四: 遍歷垃圾回收器的 buffer 緩衝區,釋放此 value。

3、帶你看源碼

1. 垃圾管家

我稱 _zend_gc_globals 爲垃圾管家,結構體會對垃圾進行管理,收集到的可能成爲垃圾的 value 就保存在這個結構的 buf 中,稱爲垃圾緩存區。

文件路徑:\Zend\zend_gc.h。

垃圾管家

2. 垃圾管家初始化

(1)php.ini 解析後調用 gc_init() 初始垃圾管家_zend_gc_globals

文件路徑:\Zend\zend_gc.c

初始垃圾管家

(2)gc_init() 函數裏面調用 gc_reset() 函數初始化。

gc_reset() 函數初始化

3. 判斷是否須要收集

(1)在銷燬一個變量時就會判斷是否須要收集。調用 i_zval_ptr_dtor() 函數

​ 文件路徑:Zend\zend_variables.h

核心邏輯

  • 若是 refcount 減一後,refcount 等於 0,則認爲不是垃圾,調用 _zval_dtor_func 方法釋放此 value。

  • 若是 refcount 減一後,refcount 大於 0,則認爲 value 多是垃圾,垃圾管家進行收集

3. 收集垃圾

文件路徑:\Zend\zend_gc.c,調用方法:gc_possible_root

gc_possible_root

  • 拿出 unused 指向的節點。
  • 若是拿出的節點是可用的,則將 unused 指向下一個節點。
  • 若是 unused 沒有可用的,且 first_unused 尚未推動到 last_unused,則表示 buf 緩存區中還有可用的節點。
  • 拿出 first_unused 指向的節點。
  • first_unused 指向下一個節點。
  • buf 緩存區已滿,啓動垃圾鑑定、垃圾回收。
  • 若是未啓用垃圾回收,則直接返回。

mark

  • 將插入的變量標爲紫色,防止重複插入。
  • 將該節點在 buf 數組中的位置保存到了 gc_info 中,當後續 value 的 refcount 變爲了 0。
  • 須要將其從 buf 中刪除時能夠知道該 value 保存在哪一個 gc_root_buffer 中。

5. 釋放垃圾

因爲回收方法 zend_gc_collect_cycles() 實在是太長,我把幾個關鍵步驟理出來了:

mark

  • 掃描根節點

  • 收集根節點

  • 調用回收器

  • 清理變量

  • 收集完成

4、總結

(1)PHP 的垃圾回收和 Java 的垃圾回收仍是頗有很大區別的,咱們都覺得沒有高級語言會用到引用計數法來回收垃圾,但恰恰 PHP 用的是引用計數。

(2)PHP 用了一套本身的算法來解決因循環引用而產生垃圾的問題,這套算法能夠簡單理解爲先把可疑垃圾的引用計數減一來進行測試,若是引用計數確實等於 0 ,則標記顏色爲黑色,後續一塊兒清理。

(3)PHP 垃圾收集中總共用到了三種關鍵顏色: 白色- 垃圾, 黑色- 非垃圾, 紫色- 防止重複插入。

相關文章
相關標籤/搜索