內存管理 - 變量的自動GC機制

前言討論GC實現

在C/C++中,如果我們想申明一個變量,就必須手動進行內存的分配與釋放。變量的內存管理是一件極度繁瑣的事情,也極度考驗開發者的寫代碼細心程度。

稍有不慎,就會忘記釋放掉,導致不可預知錯誤。現在的高級語言基本是上都提供了變量自動GC機制,由語言自己進行管理,讓開發者解脫出來。

我們先自己思考下,如果是我們來編寫內核,怎麼自動實現GC。

最簡單的實現方式:在定義變量時候分配一塊內存,用於保存Zval及Value結構,如果做爲函數,類的方法的入參,或者賦值給了其他的變量,則把變量複製一份,變量之間相互獨立,不會出現衝突。這種方式最爲簡單,

但是深拷貝帶來的一個問題,就是內存浪費嚴重,例如我們$b = $a ,$b以後進行的只是只讀操作,那麼是對內存一種極大浪費。這裏有種比較通用的解決方法:引用計數+寫時分離。當時多個變量使用同一個value時候,記

錄有多少個變量在使用,當某個value發生改變時,無法繼續使用同一個變量,這時候進行深拷貝分離value,進行寫時分離。

引用計數

引用計數用來記錄當前有多少zval指向zend_value,當有新的zval指向這個value時候,計數器+1;當zval銷燬時,計數器-1;當引用計數爲0時,表示沒有變量指向,這時候可以對zval進行釋放了。
字符串的引用計數

//zend_types.h


typedef struct _zend_refcounted_h {
   uint32_t         refcount;       /* reference counter 32-bit */
   union {
      struct {
         ZEND_ENDIAN_LOHI_3(
            zend_uchar    type,
            zend_uchar    flags,    /* used for strings & objects */
            uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
      } v;
      uint32_t type_info;
   } u;
} zend_refcounted_h;
<?php
//示例1 :內部字符串
$a = "hello world";
$c = $a;
echo "a的引用計數".PHP_EOL;
xdebug_debug_zval('a');  // a: (refcount=0, is_ref=0)='hello world'

//示例2:正常字符串
$d = "hello world".time();
$e = $d;
echo "d的引用計數".PHP_EOL;
xdebug_debug_zval('d'); // d: (refcount=2, is_ref=0)='hello world1543757673'

//示例3:產生了引用類型
$f = "hello world";
$g = &$f;
echo "f的引用計數".PHP_EOL;
xdebug_debug_zval('f');  // f: (refcount=2, is_ref=1)='hello world'

 

 並不是所有類型都會使用到引用計數,沒有具體zend_value類型是不會用到的。比如整型,浮點型,布爾型,NULL,他們的值直接使用zval保存,所以不會共用zend_value。不過除了這些還有一些特殊情況,例如示例1:內部字符串情況。還有計數器 = 2的不可變數組情況。

  • interned string : 對於 內部字符串 而言,字符串的內容是唯一不變的,相當於 C 語言中定義在靜態變量區的字符串, 他們的生存週期存在於整個請求期間,request 完成後會統一銷燬釋放 ,自然也就無需通過引用計數進行內存管理。
  • immutatable array : 不可變數組和我們上面講到的 內部字符串 一樣,都是 不使用引用計數 的,但是不同點是,內部字符串的計數值恆爲 0,而不可變數組會使用一個 僞計數值 2。

數組的引用計數

<?php
//正常數組計數
$a = [1,time()];
$b = &$a;
$c = $a;
$d = $a;
echo "a的引用計數".PHP_EOL;
xdebug_debug_zval('a');  // a: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=1543757367)
echo "b的引用計數".PHP_EOL;
xdebug_debug_zval('b');  // b: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=1543757367)
echo "c的引用計數".PHP_EOL;
xdebug_debug_zval('c');  // c: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=1543757367)

//不可變數組計數
$a1 = [1,2];
$b1 = &$a1;
$c1 = $a1;
$d1 = $a1;
echo "a1的不可變引用計數".PHP_EOL;
xdebug_debug_zval('a1');  // a1: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)
echo "b1的不可變引用計數".PHP_EOL;
xdebug_debug_zval('b1');  // b1: (refcount=2, is_ref=1)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)
echo "c1的不可變引用計數".PHP_EOL;
xdebug_debug_zval('c1');  //c1: (refcount=4, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)

 

那麼那些類型會使用引用計數呢

type refcounted
simple types  
string Y
interned string  
array Y
immutable array Y
object Y
resource Y
reference Y

寫時複製

<?php
$a = [1,2];
$b = $a;
$c = $a;
echo "a 沒有分離之前".PHP_EOL;
xdebug_debug_zval('a');  //a: (refcount=4, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)
$c = [2,3];
echo "a 沒有分離之後".PHP_EOL;
xdebug_debug_zval('a');  // a: (refcount=3, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1, 1 => (refcount=0, is_ref=0)=2)


示意圖

然而並不是所有類型的value都可以進行復制,比如對象,資源無法進行復制,也就是另一個對象修改了屬性,另一個也修改了。

<?php
//對象不會複製
$f = (object)['name'=>'li'];
$g = $f;
$g->name = "l";
var_dump($f);
var_dump($g);




/*class stdClass#1 (1) {
  public $name =>
  string(3) "l"
}
class stdClass#1 (1) {
  public $name =>
  string(3) "l"
}*/

支持複製的value類型

 

type refcounted
simple types  
string Y
interned string  
array Y
immutable array Y
object  
resource  
reference  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章