一、概念
垃圾回收機制是一種動態儲存分配的方案。它會自動釋放程式不再需要的已分配的記憶體塊。垃圾回收機制可以讓程式設計師不必過分關心程式記憶體分配,從而將更多的精力投入到業務邏輯。在現在的流行各種語言當中,垃圾回收機制是新一代語言所共有的特徵,如Python、PHP、C#、Ruby等都使用了垃圾回收機制。
二、PHP垃圾回收機制1、在PHP5.3版本之前,使用的垃圾回收機制是單純的“引用計數”。即:
①每個記憶體物件都分配一個計數器,當記憶體物件被變數引用時,計數器+1;
②當變數引用撤掉後(執行unset()後),計數器-1;
並且PHP在一個生命週期結束後就會釋放此程序/執行緒所佔的內容,這種方式決定了PHP在前期不需要過多考慮記憶體的洩露問題。
但是當兩個或多個物件互相引用形成環狀後,記憶體物件的計數器則不會消減為0;這時候,這一組記憶體物件已經沒用了,但是不能回收,從而導致記憶體洩露的現象。
php5.3開始,使用了新的垃圾回收機制,在引用計數基礎上,實現了一種複雜的演算法,來檢測記憶體物件中引用環的存在,以避免記憶體洩露。
2、隨著PHP的發展,PHP開發者的增加以及其所承載的業務範圍的擴大,在PHP5.3中引入了更加完善的垃圾回收機制,新的垃圾回收機制解決了無法處理迴圈的引用記憶體洩漏問題。
如官方文件所說:每個php變數存在一個叫"zval"的變數容器中。一個zval變數容器,除了包含變數的型別和值,還包括兩個位元組的額外資訊。第一個是"is_ref",是個bool值,用來標識這個變數是否是屬於引用集合(reference set)。通過這個位元組,php引擎才能把普通變數和引用變數區分開來,由於php允許使用者通過使用&來使用自定義引用,zval變數容器中還有一個內部引用計數機制,來優化記憶體使用。第二個額外位元組是"refcount",用以表示指向這個zval變數容器的變數(也稱符號即symbol)個數。所有的符號存在一個符號表中,其中每個符號都有作用域(scope)。簡單的理解如下圖所示:
如官方文件所說,可以使用Xdebug來檢查引用計數情況:
<?php$a = "new string";$c = $b = $a;xdebug_debug_zval( 'a' );unset( $b, $c );xdebug_debug_zval( 'a' );?>
以上例程會輸出:
a: (refcount=3, is_ref=0)='new string'a: (refcount=1, is_ref=0)='new string'
注意:從PHP7的NTS版本開始,以上例程的引用將不再被計數,即$c=$b=$a之後a的引用計數也是1.具體分類如下:
在PHP 7中,zval可以被引用計數或不被引用。在zval結構中有一個標誌確定了這一點。
①對於null,bool,int和double的型別變數,refcount永遠不會計數;
②對於物件、資源型別,refcount計數和php5的一致;
③對於字串,未被引用的變數被稱為“實際字串”。而那些被引用的字串被重複刪除(即只有一個帶有特定內容的被插入的字串)並保證在請求的整個持續時間記憶體在,所以不需要為它們使用引用計數;如果使用了opcache,這些字串將存在於共享記憶體中,在這種情況下,您不能使用引用計數(因為我們的引用計數機制是非原子的);
④對於陣列,未引用的變數被稱為“不可變陣列”。其陣列本身計數與php5一致,但是數組裡面的每個鍵值對的計數,則按前面三條的規則(即如果是字串也不在計數);如果使用opcache,則程式碼中的常量陣列文字將被轉換為不可變陣列。再次,這些生活在共享記憶體,因此不能使用refcounting。
我們的demo例子如下:
<?phpecho '測試字串引用計數';$a = "new string";$b = $a;xdebug_debug_zval( 'a' );unset( $b);xdebug_debug_zval( 'a' );$b = &$a;xdebug_debug_zval( 'a' );echo '測試陣列引用計數';$c = array('a','b');xdebug_debug_zval( 'c' );$d = $c;xdebug_debug_zval( 'c' );$c[2]='c';xdebug_debug_zval( 'c' );echo '測試int型計數';$e = 1;xdebug_debug_zval( 'e' );
看到的輸出如下:
三、回收週期
預設的,PHP的垃圾回收機制是開啟的,然後有個php.ini設定允許你修改它:zend.enable_gc 。
當垃圾回收機制開啟時,演算法會判斷每當根快取區存滿時,就會執行迴圈查詢。根快取區有固定的大小,預設10,000,可以通過修改PHP原始碼檔案Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然後重新編譯PHP,來修改這個值。當垃圾回收機制關閉時,迴圈查詢演算法永不執行,然而,根將一直存在根緩衝區中,不管在配置中垃圾回收機制是否啟用。
除了修改配置zend.enable_gc ,也能通過分別呼叫gc_enable() 和 gc_disable()函式在執行php時來開啟和關閉垃圾回收機制。呼叫這些函式,與修改配置項來開啟或關閉垃圾回收機制的效果是一樣的。即使在可能根緩衝區還沒滿時,也能強制執行週期回收。你能呼叫gc_collect_cycles()函式達到這個目的。這個函式將返回使用這個演算法回收的週期數。
允許開啟和關閉垃圾回收機制並且允許自主的初始化的原因,是由於你的應用程式的某部分可能是高時效性的。在這種情況下,你可能不想使用垃圾回收機制。當然,對你的應用程式的某部分關閉垃圾回收機制,是在冒著可能記憶體洩漏的風險,因為一些可能根也許存不進有限的根緩衝區。因此,就在你呼叫gc_disable()函式釋放記憶體之前,先呼叫gc_collect_cycles()函式可能比較明智。因為這將清除已存放在根緩衝區中的所有可能根,然後在垃圾回收機制被關閉時,可留下空緩衝區以有更多空間儲存可能根。
四、效能影響1、記憶體佔用空間的節省
首先,實現垃圾回收機制的整個原因是為了一旦先決條件滿足,通過清理迴圈引用的變數來節省記憶體佔用。在PHP執行中,一旦根緩衝區滿了或者呼叫gc_collect_cycles() 函式時,就會執行垃圾回收。
2、執行時間增加
垃圾回收影響效能的第二個領域是它釋放已洩漏的記憶體耗費的時間。
通常,PHP中的垃圾回收機制,僅僅在迴圈回收演算法確實執行時會有時間消耗上的增加。但是在平常的(更小的)指令碼中應根本就沒有效能影響。
3、在平常指令碼中有迴圈回收機制執行的情況下,記憶體的節省將允許更多這種指令碼同時執行在你的伺服器上。因為總共使用的記憶體沒達到上限。
這種好處在長時間執行指令碼中尤其明顯,諸如長時間的測試套件或者daemon指令碼此類。同時,對通常比Web指令碼執行時間長的指令碼應用程式,新的垃圾回收機制,應該會大大改變一直以來認為記憶體洩漏問題難以解決的看法。