• php5.3新垃圾回收机制详解


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

    变量对应的值,比如 $a="abc"  abc在内核是以一个zval 的形式存在的,下面列出其zval 的定义

    287 typedef struct _zval_struct zval;

    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;

    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 };

    变量$a本身也要开辟一段内存,在另外一篇文章里会讲到 变量的内存分配,这里先略过

    $a="abc";

    内核使用宏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=0

    $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

  • 相关阅读:
    C语言 指针
    C语言 字符串指针和字符串数组使用区别
    perl和python3 同时打开两个文件
    Java反射初探123456789
    电脑打不开CHM格式文件解决办法
    Spring启动报8080端口被占用问题
    SpringBoot复习
    项目开发文档编写规范
    Java核心编程-第一卷
    转载——类文件系列
  • 原文地址:https://www.cnblogs.com/taek/p/3775539.html
Copyright © 2020-2023  润新知