PHP的垃圾回收機制(開啓垃圾回收機制後的優缺點是什麼)

PHP的垃圾回收機制(開啓垃圾回收機制後的優缺點是什麼)

1、總結

一句話總結:

拿時間換空間:針對內存泄露的狀況,能夠節省大量的內存空間,可是因爲垃圾回收算法運行耗費時間,開啓垃圾回收算法會增長腳本的執行時間

 

一、如何開啓和關閉php的垃圾回收機制?

能夠經過修改php配置實現,也能夠在程序中使用gc_enable() 和 gc_disable()開啓和關閉

 

二、php中變量的存儲方式是怎樣(php中變量存在於一個zval的變量容器中)?

類型,值,is_ref字段,refcount字段
is_ref:是個bool值,用來區分變量是否屬於引用集合。什麼意思呢,你能夠這麼認爲:表示變量是否有一個以上的別名。
refcount:計數器,表示指向這個zval變量容器的變量個數。

 

三、如何打印php變量的結構?

安裝xdebug拓展以後,能夠利用xdebug_debug_zval打印出zval容器(變量結構)詳情。
<?php 
$a = 1;
xdebug_debug_zval('a');
echo PHP_EOL;
$b = $a;
xdebug_debug_zval('a');
echo PHP_EOL;

$c = &$a;
xdebug_debug_zval('a');
echo PHP_EOL;

xdebug_debug_zval('b');
echo PHP_EOL;
?>

 

 

四、php中變量賦值操做時,變量的結構如何變化?

將一個變量 = 賦值給另外一個變量時,不會當即爲新變量分配內存空間,而是在原變量的zval中給refcount加1。 只有當原變量或者發生改變時,纔會爲新變量分配內存空間,同時原變量的refcount減 1 。

 

五、php中unset原變量,變量的結構如何變化?

若是unset原變量,新變量直接就使用原變量的zval而不是從新分配。

 

六、php中&引用賦值時,變量的結構如何變化?

&引用賦值時,原變量的is_ref 變爲1,refcount 加1. 若是給一個變量&賦值,以前 = 賦值的變量會分配空間。

 

七、在5.2及更早版本的PHP中,沒有專門的垃圾回收器GC(Garbage Collection),是如何實現簡單的GC的?

引擎在判斷一個變量空間是否可以被釋放的時候是依據這個變量的zval的refcount的值,若是refcount爲0,那麼變量的空間能夠被釋放,不然就不釋放,這是一種很是簡單的GC實現。

 

八、php5.2內存泄漏實例?

環狀引用:$a = array( 'one' ); $a[] =& $a; 如今unset ($a),那麼array的refcount減1變爲1.如今無任何變量指向這個zval,可是這個zval的計數器仍然爲1,不會回收。直到用戶請求結束纔會回收。

 

九、用戶請求結束時,php中的全部變量和數據結構是否被釋放?

是的,會自動釋放的,內存泄漏發生在用戶請求結束前

 

十、PHP5.3中如何處理環狀引用致使內存泄露的問題($a = array( 'one' ); $a[] =& $a; unset ($a))?

當一個zval可能爲垃圾時,回收算法會把這個zval放入一個內存緩衝區。再視狀況看是否強制回收。

當緩衝區達到最大臨界值時(最大值能夠設置),回收算法會循環遍歷全部緩衝區中的zval,判斷其是否爲垃圾,並進行釋放處理。或者咱們在腳本中使用gc_collect_cycles,強制回收緩衝區中的垃圾。php

 

 

 

2、PHP的垃圾回收機制詳解(轉)

轉自:PHP的垃圾回收機制詳解
https://www.cnblogs.com/taijun/p/4206770.htmlhtml

最近因爲使用php編寫了一個腳本,模擬實現了一個守護進程,所以須要深刻理解php中的垃圾回收機制。本文參考了PHP手冊。算法

在理解PHP垃圾回收機制(GC)以前,先了解一下變量的存儲。數組

php中變量存在於一個zval的變量容器中。結構以下:數據結構

 

 

類型性能

spa

is_refdebug

refcountcode

 

 

zval中,除了存儲變量的類型和值以外,還有is_ref字段和refcount字段。htm

  • is_ref:是個bool值,用來區分變量是否屬於引用集合。什麼意思呢,你能夠這麼認爲:表示變量是否有一個以上的別名。 
  • refcount:計數器,表示指向這個zval變量容器的變量個數。 

二者之間有這麼一個默認關係:當refcount值爲1時,is_ref的值爲false。由於refcount爲1,此變量不可能有多個別名,也就不存在引用了。

安裝xdebug拓展以後,能夠利用xdebug_debug_zval打印出zval容器詳情。

這裏有一點須要注意,將一個變量 = 賦值給另外一個變量時,不會當即爲新變量分配內存空間,而是在原變量的zval中給refcount加1。 只有當原變量或者發生改變時,纔會爲新變量分配內存空間,同時原變量的refcount減 1 。固然,若是unset原變量,新變量直接就使用原變量的zval而不是從新分配。

 &引用賦值時,原變量的is_ref 變爲1,refcount 加1.  若是給一個變量&賦值,以前 = 賦值的變量會分配空間。

 

<?php 
$a = 1;
xdebug_debug_zval('a');
echo PHP_EOL;
$b = $a;
xdebug_debug_zval('a');
echo PHP_EOL;

$c = &$a;
xdebug_debug_zval('a');
echo PHP_EOL;

xdebug_debug_zval('b');
echo PHP_EOL;
?>

  運行結果以下:

a:(refcount=1, is_ref=0),int 1

a:(refcount=2, is_ref=0),int 1

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

b:(refcount=1, is_ref=0),int 1

 

 

上面描述的zval存儲的是標量,那複合類型的數組是如何存儲的呢?

<?php 
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
echo PHP_EOL;
class Test{
	public $a = 1;
	public $b = 2;
	
	function handle(){
		echo 'hehe';
	}
}

$test = new Test();
xdebug_debug_zval('test');
?>

  運行結果以下:

a:(refcount=1, is_ref=0),

array
  'meaning' => (refcount=1, is_ref=0),

string

'life' (length=4)
  'number' => (refcount=1, is_ref=0),

int

 42

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

object(Test)[1]
  public 'a' => (refcount=2, is_ref=0),

int

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

int

2
 

能夠看出,數組用了比數組長度多1個zval存儲。對象相似。下面給出了數組的存儲形象表示

能夠看到:數組分配了三個zval容器:a   meaning  number

如今看看所謂的環狀引用是如何生成的

<?php
$a = array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );
?>

  運行結果:

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

array
  0 => (refcount=1, is_ref=0),

string

 'one' (length=3)
  1 => (refcount=2, is_ref=1), &array

a 和 1 的zval容器 是同樣的。以下:

 

 

這樣就造成了環狀引用。

在5.2及更早版本的PHP中,沒有專門的垃圾回收器GC(Garbage Collection),引擎在判斷一個變量空間是否可以被釋放的時候是依據這個變量的zval的refcount的值,若是refcount爲0,那麼變量的空間能夠被釋放,不然就不釋放,這是一種很是簡單的GC實現。

如今unset ($a),那麼array的refcount減1變爲1.如今無任何變量指向這個zval,並且這個zval的計數器爲1,不會回收。

 

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

 

若是上面的狀況發生僅僅一兩次倒沒什麼,可是若是出現幾千次,甚至幾十萬次的內存泄漏,這顯然是個大問題。在長時間運行的腳本,好比請求基本上不會結束的守護進程時,就會出現問題,內存空間會不斷耗費,致使內存不足而崩潰。

 

PHP5.3中,採用了專門的算法(比較複雜)。,來處理環狀引用致使內存泄露的問題。

當一個zval可能爲垃圾時,回收算法會把這個zval放入一個內存緩衝區。當緩衝區達到最大臨界值時(最大值能夠設置),回收算法會循環遍歷全部緩衝區中的zval,判斷其是否爲垃圾,並進行釋放處理。或者咱們在腳本中使用gc_collect_cycles,強制回收緩衝區中的垃圾。

在php5.3的GC中,針對的垃圾作了以下說明:

1:若是一個zval的refcount增長,那麼此zval還在使用,確定不是垃圾,不會進入緩衝區

2:若是一個zval的refcount減小到0, 那麼zval會被當即釋放掉,不屬於GC要處理的垃圾對象,不會進入緩衝區。

 3:若是一個zval的refcount減小以後大於0,那麼此zval還不能被釋放,此zval可能成爲一個垃圾,將其放入緩衝區。PHP5.3中的GC針對的就是這種zval進行的處理。

 

 

開啓/關閉垃圾回收機制能夠經過修改php配置實現,也能夠在程序中使用gc_enable() 和 gc_disable()開啓和關閉。

 

開啓垃圾回收機制後,針對內存泄露的狀況,能夠節省大量的內存空間,可是因爲垃圾回收算法運行耗費時間,開啓垃圾回收算法會增長腳本的執行時間。

下面是php手冊中給的一個腳本

<?php
class Foo
{
    public $var = '3.1415962654';
}

$baseMemory = memory_get_usage();

for ( $i = 0; $i <= 100000; $i++ )
{
    $a = new Foo;
    $a->self = $a;
    if ( $i % 500 === 0 )
    {
        echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";
    }
}
?>

  

針對這個腳本,給出了其在php5.2和5.3中內存的佔用狀況,以下圖:

針對下面這個腳本

<?php
class Foo
{
    public $var = '3.1415962654';
}

for ( $i = 0; $i <= 1000000; $i++ )
{
    $a = new Foo;
    $a->self = $a;
}

echo memory_get_peak_usage(), "\n";
?>

  

開啓垃圾回收機制,相對於不開啓的時候,腳本執行時間增長了7%

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

相關文章
相關標籤/搜索