PHP垃圾回收機制

1、引用計數基本知識php

  每一個php變量存在一個叫"zval"變量容器中,當一個變量被賦常量值時,就會生成一個zval變量容器。一個zval變量容器,除了包含變量的類型和值,還包括兩個字節的額外信息。第一個是"is_ref",是個bool值,用來標識這個變量是不是屬於引用集合(reference set)。經過這個字節,php引擎才能把普通變量和引用變量區分開來,因爲php容許用戶經過使用&來使用自定義引用,zval變量容器中還有一個內部引用計數機制,來優化內存使用。第二個額外字節是"refcount",用以表示指向這個zval變量容器的變量(也稱符號即symbol)個數。全部的符號存在一個符號表中,其中每一個符號都有做用域(scope),那些主腳本(好比:經過瀏覽器請求的的腳本)和每一個函數或者方法也都有做用域。算法

  當把一個變量賦值給另外一變量將增長引用次數(refcount)。不必時,php不會去複製已生成的變量容器。變量容器在」refcount「變成0時就被銷燬當任何關聯到某個變量容器的變量離開它的做用域(好比:函數執行結束),或者對變量調用了函數 unset()時,」refcount「就會減1數組

  若是你已經安裝了Xdebug,你能經過調用函數xdebug_debug_zval()顯示"refcount""is_ref"的值。瀏覽器

  當考慮像 arrayobject這樣的複合類型時,事情就稍微有點複雜.。與 標量(scalar)類型的值不一樣,array和 object類型的變量把它們的成員或屬性存在本身的符號表中。緩存

數組和內存示例:服務器

1)通常狀況:數據結構

$a = array( 'meaning' => 'life', 'number' => 42 );函數

(2)數組中有兩個元素的值相同:性能

$a = array( 'meaning' => 'life', 'number' => 42 );單元測試

$a['life'] = $a['meaning'];

(3)將數組自己做爲本身的一個元素:

$a = array( 'one' );

$a[] =& $a;

執行unset($a);以後:

 

  在第三種狀況中,儘管再也不有某個做用域中的任何符號指向這個結構(就是變量容器),因爲數組元素「1」仍然指向數組自己,因此這個容器不能被清除 。由於沒有另外的符號指向它,用戶沒有辦法清除這個結構,結果就會致使內存泄漏。慶幸的是,php將在腳本執行結束時清除這個數據結構,可是在php清除以前,將耗費很多內存。若是你要實現分析算法,或者要作其餘像一個子元素指向它的父元素這樣的事情,這種狀況就會常常發生。固然,一樣的狀況也會發生在對象上,實際上對象更有可能出現這種狀況,由於對象老是隱式的被引用。

  若是上面的狀況發生僅僅一兩次倒沒什麼,可是若是出現幾千次,甚至幾十萬次的內存泄漏,這顯然是個大問題。這樣的問題每每發生在長時間運行的腳本中,好比請求基本上不會結束的守護進程或者單元測試中的大的套件中。後者的例子:在給巨大的eZ(一個知名的PHP Library) 組件庫的模板組件作單元測試時,就可能會出現問題。有時測試可能須要耗用2GB的內存,而測試服務器極可能沒有這麼大的內存。

 

2、回收週期  

  PHP5.3以前使用的垃圾回收機制是單純的「引用計數」,也就是爲每一個內存對象都分配一個引用計數器,這樣的機制存在一個問題,就是當兩個或多個對象互相引用造成環狀時,會出現內存泄露現象。PHP5.3開始,使用了新的垃圾回收機制,在引用計數的基礎上,實現了一種複雜的算法,來檢測內存對象中引用環的存在,以免內存泄露。

  算法的一些基本規則:若是一個引用計數增長,它將繼續被使用,固然就再也不在垃圾中。若是引用計數減小到零,所在變量容器將被清除。就是說,僅僅在引用計數減小到非零值時,纔會產生垃圾週期。其次,在一個垃圾週期中,經過檢查引用計數是否減1,而且檢查哪些變量容器的引用次數是零,來發現哪部分是垃圾。

  爲避免不得不檢查全部引用計數可能減小的垃圾週期,這個算法把全部可能根(都是zval變量容器),放在根緩衝區中(用紫色來標記,稱爲疑似垃圾),這樣能夠同時確保每一個可能的垃圾根在緩衝區中只出現一次。僅僅在根緩衝區滿了時,纔對緩衝區內部全部不一樣的變量容器執行垃圾回收操做。看上圖的步驟 A。

  在步驟 B 中,模擬刪除每一個紫色變量。模擬刪除時可能將不是紫色的普通變量引用數減"1",若是某個普通變量引用計數變成0了,就對這個普通變量再作一次模擬刪除。每一個變量只能被模擬刪除一次,模擬刪除後標記爲灰色。

  在步驟 C 中,模擬恢復每一個紫色變量。恢復是有條件的,當變量的引用計數大於0時纔對其作模擬恢復。一樣每一個變量只能恢復一次,恢復後標記爲黑,基本就是步驟 B 的逆運算(將普通變量引用數加"1")。這樣剩下的一堆沒能恢復的就是該刪除的藍色節點了,在步驟 D 中遍歷出來真的刪除掉。

  算法中都是模擬刪除、模擬恢復、真的刪除,都使用簡單的遍歷便可(最典型的深搜遍歷)。複雜度爲執行模擬操做的節點數正相關,不僅是紫色的那些疑似垃圾變量。

默認的,PHP的垃圾回收機制是打開的,你能夠在配置文件php.ini中修改它:zend.enable_gc 。

  當垃圾回收機制打開時,每當根緩存區存滿時,就會執行上面描述的循環查找算法。根緩存區有固定的大小,可存10,000個可能根,固然你能夠經過修改PHP源碼文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,而後從新編譯PHP,來修改這個10,000值。當垃圾回收機制關閉時,循環查找算法永不執行,然而,可能根將一直存在根緩衝區中,無論在配置中垃圾回收機制是否激活。

  當垃圾回收機制關閉時,若是根緩衝區存滿了可能根,更多的可能根顯然不會被記錄。那些沒被記錄的可能根,將不會被這個算法來分析處理。若是他們是循環引用週期的一部分,將永不能被清除進而致使內存泄漏。

  即便在垃圾回收機制不可用時,可能根也被記錄的緣由是,相對於每次找到可能根後檢查垃圾回收機制是否打開而言,記錄可能根的操做更快。不過垃圾回收和分析機制自己要耗很多時間。

  除了修改配置zend.enable_gc ,也能經過分別調用gc_enable()  gc_disable()函數來打開和關閉垃圾回收機制。調用這些函數,與修改配置項來打開或關閉垃圾回收機制的效果是同樣的。即便在可能根緩衝區還沒滿時,也能強制執行週期回收。你能調用gc_collect_cycles()函數達到這個目的。這個函數將返回使用這個算法回收的週期數。

  容許打開和關閉垃圾回收機制而且容許自主的初始化的緣由,是因爲你的應用程序的某部分多是高時效性的。在這種狀況下,你可能不想使用垃圾回收機制。固然,對你的應用程序的某部分關閉垃圾回收機制,是在冒着可能內存泄漏的風險,由於一些可能根也許存不進有限的根緩衝區。所以,就在你調用gc_disable()函數釋放內存以前,先調用gc_collect_cycles()函數可能比較明智。由於這將清除已存放在根緩衝區中的全部可能根,而後在垃圾回收機制被關閉時,可留下空緩衝區以有更多空間存儲可能根。

 

3、性能方面考慮的因素

  相比較於PHP5.2,PHP5.3採用的這種垃圾回收機制對性能的影響主要體如今兩個方面上:第一是內存佔用空間的節省,第二是垃圾回收機制執行內存清理時的執行時間增長。

  一般,PHP中的垃圾回收機制,僅僅在循環回收算法確實運行時會有時間消耗上的增長。可是在日常的(更小的)腳本中應根本就沒有性能影響。

  然而,在日常腳本中有循環回收機制運行的狀況下,內存的節省將容許更多這種腳本同時運行在你的服務器上。由於總共使用的內存沒達到上限。這種好處在長時間運行腳本中尤爲明顯,諸如長時間的測試套件或者daemon腳本此類。