php5和php7垃圾回收的區別


前言:之前對PHP的GC只是了解了個大概,這次詳細了解下PHP的垃圾回收機制(GC)。
   介於網上大部分都是PHP5.X的GC,雖然 php5 到 php7 GC部分做出的改動較小,但我覺得還是一起寫下來比較好

 

一、原理

php5和php7的垃圾回收機制都是利用引用計數

 

二、php5和php7不同點

1、PHP5標量數據類型會計數,PHP7標量數據類型不再計數,不需要單獨分配內存
2、PHP7的zval 需要的內存不再是單獨從堆上分配,不再自己存儲引用計數。
3、PHP7的復雜數據類型(比如數組和對象)的引用計數由其自身來存儲。

 

三、變量在zval的變量容器中結構

zval中,除了存儲變量的類型和值之外,還有is_ref字段和refcount字段
    1、is_ref:是個bool值,用來區分變量是否屬於引用集合。
    2、refcount:計數器,表示指向這個zval變量容器的變量個數。 

 

四、PHP5.3標量在zval容器例子

 注意:php5.3中將一個變量 = 賦值給另一個變量時,不會立即為新變量分配內存空間,而是在原變量的zval中給refcount加1。 只有當原變量或者發生改變時,才會為新變量分配內存空間,同時原變量的refcount減 1 。當然,如果unset原變量,新變量直接就使用原變量的zval而不是重新分配。&引用賦值時,原變量的is_ref  加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

復制代碼

 

 

五、PHP7.X 標量在zval容器例子

復制代碼
<?php

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

結果如下:可以看到標量(布爾,字符串,整形,浮點型)不再計數了
復制代碼

六、PHP5.3復合類型數組和對象在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');

結果如下:可以看出,數組用了比數組長度多1個zval存儲。數組分配了三個zval容器:a   meaning  number

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
復制代碼

 

七、PHP7.X復合類型數組和對象在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=2,后經測試發現數組的refcount都是從2開始的
復制代碼

八、循環引用問題

  1、PHP7.1效果

復制代碼
<?php

$a = array('life');
xdebug_debug_zval( 'a' );
echo PHP_EOL;
$a[] = &$a;
xdebug_debug_zval('a');
復制代碼

可以看到,箭頭方向表示的就是遞歸循環引用了

  2、再看看5.3的效果

   

復制代碼
說明:在5.2及更早版本的PHP中,沒有專門的垃圾回收器GC(Garbage Collection),引擎在判斷一個變量空間是否能夠被釋放的時候是依據這個變量的zval的refcount的值,
   如果refcount為0,那么變量的空間可以被釋放,否則就不釋放,這是一種非常簡單的GC實現。現在unset ($a),那么array的refcount減1變為1.現在無任何變量指向這個zval,
   而且這個zval的計數器為1,不會回收。 結果:盡管不再有某個作用域中的任何符號指向這個結構(就是變量容器),由於子元素“1”仍然指向數組本身,所以這個容器不能被清除 。
   因為沒有另外的符號指向它,用戶沒有辦法清除這個結構,結果就會導致內存泄漏。 在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()開啟和關閉。

 

九、垃圾回收算法

復制代碼
1、對每個根緩沖區中的根zval按照深度優先遍歷算法遍歷所有能遍歷到的zval,並將每個zval的refcount減1,同時為了避免對同一zval多次減1(因為可能不同的根能遍歷到同一個zval),
  每次對某個zval減1后就對其標記為“已減”。 2、再次對每個緩沖區中的根zval深度優先遍歷,如果某個zval的refcount不為0,則對其加1,否則保持其為0。 3、清空根緩沖區中的所有根(注意是把這些zval從緩沖區中清除而不是銷毀它們),然后銷毀所有refcount為0的zval,並收回其內存。 如果不能完全理解也沒有關系,只需記住PHP5.3的垃圾回收算法有以下幾點特性: 1、並不是每次refcount減少時都進入回收周期,只有根緩沖區滿額后在開始垃圾回收。 2、可以解決循環引用問題。 3、可以總將內存泄露保持在一個閾值以下。
復制代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM