• 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

    在一个垃圾回收周期内,在变量容器列表,变量容器zval中refcout=0即执行垃圾回收。

    >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释放掉。

    相关函数

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

    总结:

    1. 在进行unset操作时,unset只是断开一个变量到一块内存区域的连接, 同时将该内存区域(变量容器zval)的引用计数(refount, php7叫zend_refcounted)进行-1。
    2. 如果引用计数已经到0, 则立刻进行该变量的回收。如果引用计数大于0, 则把改变量放入根缓存区。
    3. 当根缓存区满了的时候或者进入垃圾回收周期,启用gc算法判断是否为垃圾。
    4. 对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作后,再次遍历如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。
  • 相关阅读:
    《Effective Java》读书笔记八(异常)
    《Effective Java》读书笔记七(通用程序设计)
    《Effective Java》读书笔记六(方法)
    《Effective Java》读书笔记五(枚举和注解)
    《Effective Java》读书笔记四(泛型)
    《Effective Java》读书笔记三(类和接口)
    《Effective Java》读书笔记二(通用方法)
    AngularJS的directive(指令)配置选项说明
    angularJS常见问题汇总
    angular指令中,require和transclude同时设置为true时的作用
  • 原文地址:https://www.cnblogs.com/one-villager/p/8865403.html
Copyright © 2020-2023  润新知