php5.3新垃圾回收機制詳解

php的垃圾回收機制主要參考了http://blog.csdn.net/phpkernel/article/details/5734743 這文章。php

變量對應的值,好比 $a="abc"  abc在內核是以一個zval 的形式存在的,下面列出其zval 的定義數組

287 typedef struct _zval_struct zval;ui

307 typedef union _zvalue_value {
308 long lval; /* long value */
309 double dval; /* double value */
310 struct {
311 char *val;
312 int len;
313 } str;
314 HashTable *ht; /* hash table value */
315 zend_object_value obj;
316 } zvalue_value;spa

318 struct _zval_struct {
319 /* Variable information */
320 zvalue_value value; /* value */
321 zend_uint refcount__gc;
322 zend_uchar type; /* active type */
323 zend_uchar is_ref__gc;
324 };.net

變量$a自己也要開闢一段內存,在另一篇文章裏會講到 變量的內存分配,這裏先略過指針

$a="abc";orm

內核使用宏ALLOC_ZVAL和 INIT_ZVAL 對abc 分配一段內存,並進行初始化對象

由於有引號的出現,因此php內核會認爲這是一個字符串,因而 zval.type=IS_STRING , zval.str.val="abc", zval.str.len=3, zval.refcount_gc=1 ,zval.is_ref_gc=0blog

$b=$a; 那麼php內核不會爲 $b再分配一段內存,避免了內存浪費,而是將$b指向了 "abc"  這個zval ,  也就是說在變量符號表(HashTable實現)中 key爲b的,value 爲 "abc"所在的 zval的指針,即內存地址 這裏:"abc" 的 refcont_gc=2, is_ref_gc=0內存

$b=2; 這個時候,$b發生了變化,那麼不至於影響到 $a的使用, 內核會把 "abc"所在的zval 的 refcount_gc -1, (abc的refcount_gc=1,is_ref=0 )再把abc對應的zval 複製一份,並在變量符號表中更新key爲b 的value值,也就是常說的 COPY ON WRITE(寫時複製)  2的refcount_gc=1 , is_ref_gc=0

$a=&$b; $a會直接指i向$b對應的zval,  值爲2 的 refcount=2 , is_ref=1 

unset($a); 值爲2的 refcount=1 ,is_ref=1 注:unset,把變量$a自己 從符號表中刪除,同時refcount_gc-1,若是減後的refcount_gc爲0,則進行收回內存

 

若是 是 數組,或者對象可能就會出問題

$a=array(1); $a對應的zval : refcount_gc=1, is_ref=0 , $a[0]對應的zval: refcount_gc=1,is_ref=0

$a[]=&$a;   $a對應的zval : refcount_gc=2 ,is_ref=1 , $a[1] 對應的zval: refcount_gc=2, is_ref=1

unset($a);  符號表中的key='a'的變量刪除了,php內核嘗試將$a對應的zval:refcount_gc-1, refcount_gc結果爲1,不等於0,內核則沒法回收內存,這時發生了內存泄漏 (其實最終也回收了內存 ,當請求結束時,內核會回收所的內存,可是當php以daemon形式除外)

 

下面,php新的垃圾回收機制出場了

 先看下php5.2爲zval分配內存的方式

 內核使用宏:MAKE_STD_ZVAL(z),該宏又包含兩個宏ALLOC_ZVAL(z)和INIT_PZVAL(z)

宏ALLOC_ZVAL(z)展開後爲

(z)=(zval *)emalloc(sizeof(zval)); //位於zend_alloc.h

 

宏INIT_PZVAL(z)展開爲後://位於zend.h

(z)->refcount_gc=1;

(z)->is_ref_gc=0;

 

如今php5.3是用下面的方法

#undef ALLOC_ZVAL

#define ALLOC_ZVAL(z)

do{\

(z)=(zval *)emalloc(sizeof(zval_gc_info))\

GC_ZVAL_INIT(z)\

} while(0)

 

其中zval_gc_info是個結構體

#typedef _zval_gc_info{

  zval z;

  union{

    gc_root_buffer *buffer;

    struct _zval_gc_info *next;

  }u;

}zval_gc_info;

 

gc_root_buffet 也是個結構體

#typedef _gc_root_buffer{

  struct _gc_root_buffet *next;

  struct _gc_root_buffet *prev;

  union{

    zval  *pz;

  }u;

}gc_root_buffer;

 

仍是以這個爲例

$a=array(1);

$a[]=&$a;  

unset($a);

php 5.3垃圾處理原理是:
1)當調用unset後,將 $a對應的zval的refcount_gc減1,這時,refcount_gc=1

2)由於1>0,zval還不能被釋放 ,由於沒有變量要使用zva了,unset($a),變量$a已經爲空, 因此是垃圾數據,設置zval.u.buffer 這個指針的爲紫色,放入一個緩衝區)

2.1)若refcount_gc=0,說明沒有別的變量在使用了,能夠釋放了

3)當緩衝區滿的時候,開始在緩衝區中遍歷,將結點對應的zval的refcount_gc減1,若是節點對應的zvzl中包括的zval又指向了前面的zval(環形引用),也要refcount-1,即將zval的每個元素都將refcount_gc減1 ,爲避免重複操做,減後,將顏色置爲灰色

4)再次遍歷緩衝區,若是refcount_gc的值不爲0,說明不是垃圾,垃圾被其餘變量使用,由於將顏色爲黑色,

5)若是refcount=0,說明是垃圾,設置顏色爲白色,將回收內存

 

當緩衝區滿了之後,才進行垃圾回收,由於垃圾回收也是消耗資源的,這跟生活中的垃圾箱同樣,只有當垃圾箱滿了之後,清潔工纔過來清理垃圾。

當調用unset($a)後,內核會調用下面的方法

 

static zend_always_inline void i_zval_ptr_dtor(zval *zval_ptr ZEND_FILE_LINE_DC)
{
  if (!Z_DELREF_P(zval_ptr)) {
    TSRMLS_FETCH();

 

    ZEND_ASSERT(zval_ptr != &EG(uninitialized_zval));
    GC_REMOVE_ZVAL_FROM_BUFFER(zval_ptr);
    zval_dtor(zval_ptr);
    efree_rel(zval_ptr);
} else {
    TSRMLS_FETCH();

 

    if (Z_REFCOUNT_P(zval_ptr) == 1) {
    Z_UNSET_ISREF_P(zval_ptr);
  }  

 

  GC_ZVAL_CHECK_POSSIBLE_ROOT(zval_ptr);
}
}

 

static zend_always_inline void _zval_dtor(zval *zvalue ZEND_FILE_LINE_DC)
{
if (zvalue->type <= IS_BOOL) {
return;
}
_zval_dtor_func(zvalue ZEND_FILE_LINE_RELAY_CC);
}

zend_variables.c


ZEND_API void _zval_dtor_func(zval *zvalue ZEND_FILE_LINE_DC)
{
switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) {
case IS_STRING:
case IS_CONSTANT:
CHECK_ZVAL_STRING_REL(zvalue);
STR_FREE_REL(zvalue->value.str.val);
break;
case IS_ARRAY:
case IS_CONSTANT_ARRAY: {
TSRMLS_FETCH();

if (zvalue->value.ht && (zvalue->value.ht != &EG(symbol_table))) {
/* break possible cycles */
Z_TYPE_P(zvalue) = IS_NULL;
zend_hash_destroy(zvalue->value.ht);
FREE_HASHTABLE(zvalue->value.ht);
}
}
break;
case IS_OBJECT:
{
TSRMLS_FETCH();

Z_OBJ_HT_P(zvalue)->del_ref(zvalue TSRMLS_CC);
}
break;
case IS_RESOURCE:
{
TSRMLS_FETCH();

/* destroy resource */
zend_list_delete(zvalue->value.lval);
}
break;
case IS_LONG:
case IS_DOUBLE:
case IS_BOOL:
case IS_NULL:
default:
return;
break;
}
}

 

 

 

2428 ZEND_API void _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
2429 {
2430 TSRMLS_FETCH();
2431
2432 if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) {
2433 AG(mm_heap)->_free(ptr);
2434 return;
2435 }
2436 _zend_mm_free_int(AG(mm_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
2437 }

 

 

2428 ZEND_API void _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
2429 {
2430 TSRMLS_FETCH();
2431
2432 if (UNEXPECTED(!AG(mm_heap)->use_zend_alloc)) {
2433 AG(mm_heap)->_free(ptr);
2434 return;
2435 }
2436 _zend_mm_free_int(AG(mm_heap), ptr ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);
2437 }

zend_alloc.c

相關文章
相關標籤/搜索