PHP中的垃圾回收机制

Posted 哒哒的马蹄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PHP中的垃圾回收机制相关的知识,希望对你有一定的参考价值。

php5的Zval容器

struct _zval_struct {
    union {
        long lval;
        double dval;
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;
        zend_object_value obj;
        zend_ast *ast;
    } value;     /* 变量的值 */
    zend_uint refcount__gc; /* 引用次数 */
    zend_uchar type;        /* 变量当前的数据类型 */
    zend_uchar is_ref__gc;  /* 是否是属于引用集合 */
};

PHP7的Zval容器

struct _zval_struct {
    union {
        zend_long         lval;             /* long value */
        double            dval;             /* double value */
        zend_refcounted  *counted;
        zend_string      *str;
        zend_array       *arr;
        zend_object      *obj;
        zend_resource    *res;
        zend_reference   *ref;
        zend_ast_ref     *ast;
        zval             *zv;
        void             *ptr;
        zend_class_entry *ce;
        zend_function    *func;
        struct {
            uint32_t w1;
            uint32_t w2;
        } ww;
    }  value; /* 变量的值 */
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;    /* 简化赋值, 四个字符变量的结构体 */
        uint32_t type_info;   /* 类型信息 */
    } u1;
    union {
        uint32_t     var_flags;
        uint32_t     next;                 /* hash碰撞链 */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* 行号(AST,对象生成树槽点) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach位置 */
        uint32_t     fe_iter_idx;          /* foreach迭代器索引 */
    } u2;
};

PHP引用计数基本知识点

  • 当一个变量被赋常量值时,就会生成一个zval变量容器。
  • unset并非一定会释放内存,当有两个变量指向的时候,并非会释放变量占用的内存,只是refcount减1.

<PHP7

  • php变量存在一个叫"zval"的变量容器中, zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set),通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是"refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope)。

>PHP7

  • PHP变量容器"zval"中,zval_value 结构体中包含zend_refcounted、zend_reference分别替代了refcount,is_ref

内存管理机制

内存申请与释放设计

  • 对于php的核心结构Hashtable来说,由于未知性,定义的时候不可能一次性分配足够多的内存块。所以初始化的时候只会分配一小块,等不够的时候在进行扩容,而Hashtable只扩容不减少。
  • php并非简单的向os申请内存,而是会申请一大块内存,把其中一部分分给申请者,这样当再有逻辑来申请内存的时候,就不需要向os申请了,避免了频繁调用。当内存不够的时候才会再次申请。
  • 当释放内存的时候,php并非会把内存还给os,而是把内存轨道自己维护的空闲内存列表,以便重复利用。

内存的分配做了两件事情

  • 1.为变量名分配内存,存入符号表
  • 2.为变量值分配内存

垃圾定义

  • 判断有没有任何变量名指向变量容器zval, 如果没有则认为是垃圾,需要释放。
  • 当变量容器zval中的refcount=0时,表示没有变量名指向该容器。

内存泄漏

环形引用

<?php
    $a = ['one'];
    $a[] = &$a;
    xdebug_debug_zval('a');
    /**
     (refcount=2, is_ref=1),
        array (size=2)
          0 => (refcount=1, is_ref=0),string 'one' (length=3)
          1 => (refcount=2, is_ref=1),
     */

处理垃圾内存

PHP5.3 && <PHP7

<?php
    $a = ['one']; //--- zval_a(将$a对应的zval,命名为zval_a)
    $a[] = &$a;   //--- step1
    unset($a);    //--- step2
判断处理过程
  • 1.如果一个zval的refcount增加,那么此zval还在使用,不属于垃圾
  • 2.如果一个zval的refcount减少到0, 那么zval可以被释放掉,不属于垃圾
  • 3.如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾
仅当因此出现第3种情况时进行如下操作
  • zval容器放入缓冲区
    • 直接将此zval节点放入一个节点(root)缓冲区(root buffer),并且将这些zval节点标记成紫色。
  • 预减操作(子zval节点refcount减1)
    • 当缓冲区被节点塞满的时候(或者进入垃圾回收周期),GC才开始开始对缓冲区中的zval节点进行垃圾判断。
    • 垃圾判断算法以深度优先对节点所包含的zval进行减1操作,为了确保不会对同一个zval的refcount重复执行减1操作,一旦zval的refcount减1之后会将zval标记成灰色。在此期间,起初节点zval本身不做减1操作,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作。
  • 垃圾判断
    • 算法再次以深度优先判断每一个节点包含的zval的值,如果zval的refcount等于0,那么将其标记成白色(代表垃圾),如果zval的refcount大于0,那么将对此zval以及其包含的zval进行refcount加1操作,这个是对非垃圾的还原操作,同时将这些zval的颜色变成黑色(zval的默认颜色属性)。
  • 释放垃圾
    • 遍历zval节点,将垃圾判断过程中标记成白色的节点zval释放掉。

总结:

对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作,之后如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。

相关函数

  • gc_enable() : 开启GC
  • gc_disable() : 关闭GC
  • gc_collect_cycles() : 在节点缓冲区未满的情况下强制执行垃圾分析算法

以上是关于PHP中的垃圾回收机制的主要内容,如果未能解决你的问题,请参考以下文章

PHP中的垃圾回收机制

PHP内核之旅-6.垃圾回收机制

PHP垃圾回收机制理解

php回收机制

PHP垃圾回收机制(GC)

浅谈PHP5中垃圾回收算法