美文网首页
PHP垃圾回收理解

PHP垃圾回收理解

作者: _Henry_ | 来源:发表于2017-04-20 11:20 被阅读0次

    PHP 类型

    PHP的八种数据类型可点击查看。

    源码表示

    struct _zval_struct {  
        /* Variable information */ 
        zvalue_value value;       
        /* value */ 
        zend_uint refcount__gc;  
        zend_uchar type;    /* active type */ 
        zend_uchar is_ref__gc;  
    }; 
    

    其中zvalue_value为:

    typedef union _zvalue_value {  
        long lval;                  /* long value */ 
        double dval;                /* double value */ 
        struct {  
            char *val;  
            int len;  
        } str;  
        HashTable *ht;              /* hash table value */ 
        zend_object_value obj;  
    } zvalue_value;  
    

    其中refcount__gc表示当前有几个变量引用此zval,而is_ref__gc表示当前zval是否被按引用引用,这话听起来很拗口,这和PHP中zval的“Write-On-Copy”机制有关。

    PHP5.2中的垃圾回收算法——Reference Counting

    PHP5.2中使用的内存回收算法是大名鼎鼎的Reference Counting,这个算法中文翻译叫做“引用计数”,其思想非常直观和简洁:为每个内存对象分配一个计数器,当一个内存对象建立时计数器初始化为1(因此此时总是有一个变量引用此对象),以后每有一个新变量引用此内存对象,则计数器加1,而每当减少一个引用此内存对象的变量则计数器减1,当垃圾回收机制运作的时候,将所有计数器为0的内存对象销毁并回收其占用的内存。而PHP中内存对象就是zval,而计数器就是refcount__gc。

    例如下面一段PHP代码演示了PHP5.2计数器的工作原理(计数器值通过xdebug得到):

    //zval(val1).refcount_gc = 1;
    $val1 = 100;   
    //zval(val1).refcount_gc = 2,zval(val2).refcount_gc = 2(因为是Write on copy,当前val2与val1共同引用一个zval) 
    $val2 = $val1;  
    //zval(val1).refcount_gc = 1,zval(val2).refcount_gc = 1(此处val2新建了一个zval) 
    $val2 = 200;  
    //zval(val1).refcount_gc = 0($val1引用的zval再也不可用,会被GC回收)  
    unset($val1); 
    

    内存泄漏

    $a = array();  
    $a[] = & $a;  
    unset($a); 
    

    这段代码首先建立了数组a,然后让a的第一个元素按引用指向a,这时a的zval的refcount就变为2,然后我们销毁变量a,此时a最初指向的zval的refcount为1,但是我们再也没有办法对其进行操作,因为其形成了一个循环自引用。由于a之前指向的zval的refcount为1(被其HashTable的第一个元素引用),这个zval就不会被GC销毁,这部分内存就泄露了。

    PHP5.3回收算法改进

    PHP5.3的垃圾回收算法仍然以引用计数为基础,但是不再是使用简单计数作为回收准则,而是使用了一种同步回收算法,这个算法由IBM的工程师在论文Concurrent Cycle Collection in Reference Counted Systems中提出。

    这个算法可谓相当复杂,只能大体描述一下此算法的基本思想。

    首先PHP会分配一个固定大小的“根缓冲区”,这个缓冲区用于存放固定数量的zval,这个数量默认是10,000,如果需要修改则需要修改源代码Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES然后重新编译。

    由上文我们可以知道,一个zval如果有引用,要么被全局符号表中的符号引用,要么被其它表示复杂类型的zval中的符号引用。因此在zval中存在一些可能根(root)。这里我们暂且不讨论PHP是如何发现这些可能根的,这是个很复杂的问题,总之PHP有办法发现这些可能根zval并将它们投入根缓冲区。

    当根缓冲区满额时,PHP就会执行垃圾回收,此回收算法如下:

    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、可以总将内存泄露保持在一个阈值以下。

    相关文章

      网友评论

          本文标题:PHP垃圾回收理解

          本文链接:https://www.haomeiwen.com/subject/nxakzttx.html