php垃圾回收相关

阅读了几篇关于php垃圾回收的文章以及php5.3~php7的变化,个人总结了几点。主要是从变量结构变化以及垃圾回收机制的变化来总结:

一、变量方面

php5.3~php7
这个阶段,将变量的定义分成了

zval_struct {
    
    zval_value value;
    zend_uint ref_count_gc;
    zend_uchar is_ref_gc;
    zend_uchar type;
}
zvalue_value {
    long;
    double;
    struct {

    } str;
    Hashtable * ht;
    zend_obj_val obj;
}

Hashtable :
zend_array {
    zend_refcount_h gc;
    ....(那串复杂的结构,包括了 bucket *arData等)
}

php7整合为了一个数据结构,同时引用计数是一个结构体

typedef struct _zend_refcounted_h {
    uint32_t         refcount; // 记录 zend_value 的引用数
    union {
        struct {
            zend_uchar    type,  // zend_value的类型, 与zval.u1.type一致
            zend_uchar    flags, 
            uint16_t      gc_info // GC信息,记录在 gc 池中的位置和颜色,垃圾回收的过程会用到
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;

二、垃圾回收机制
1、以php的引用计数机制为基础(php5.3以前只有该机制)
2、同时使用根缓冲区机制,当php发现有存在循环引用的zval时,就会把其投入到根缓冲区,当根缓冲区达到配置文件中的指定数量后,就会进行垃圾回收,以此解决循环引用导致的内存泄漏问题(php5.3开始引入该机制)
3、php7采用了三次标记

其他说明:

1.php7的对于refcount的位置也已经从zval结构移到zval_value内了,所以不是所有的数据类型都会有引用计数操作,比如整型、浮点型、布尔、NULL这些简单数据类型都没有refcount了
2.php5.3~7垃圾回收使用的算法:
只有被循环引用的zval才会被放入缓冲区(具体放入的条件是:如果当变量的 refcount 减小后大于 0,PHP 并不会立即对这个变量进行垃圾鉴定和回收,而是放入一个缓冲区中),而缓冲区内引用计数为1代表的就是当前作用域内已经没有符号指向该zval,即应该被回收的垃圾。
因此,每当根缓冲区存满时,php会对根缓冲区的所有变量容器遍历来进行模拟删除,并且会在模拟删除后进行模拟恢复。但是php只会对进行模拟删除后refcount>0的进行恢复,那么没有办法恢复的也就是refcount=0的,那就是垃圾了~

3.php7的垃圾回收采用的是三色标记:

过程说明:

扫描二维码关注公众号,回复: 12861986 查看本文章

一个变量只能加入一次缓冲区,为了防止重复加入,变量加入后会把 zend_refcounted_h.gc_info 置为 GC_PURPLE,即标为紫色,后续不会重复插入。

垃圾缓冲区是一个双向链表,等到缓存区满了以后则启动垃圾检查过程:遍历缓冲区,对当前变量的所有成员进行遍历,然后把成员的 refcount 减 1 (如果成员还包含子成员则也进行递归遍历,即深度优先遍历),最后再检查当前变量的引用,如果减为了 0 则为垃圾。这个算法的原理核心是:垃圾是由于成员引用自身导致的,那么就对所有的成员减一遍引用,如果发现最后变量本身的 refcount 变为了 0 则就表明其引用全部来自自身成员,即其他任何地方都不再使用它,那么它就是垃圾,需要被回收掉。反之说明不是垃圾,需要将其从缓冲区移出去。具体的过程如下:

(1) 从缓冲区链表的 roots 开始遍历,把当前 value 标为灰色 (zend_refcounted_h.gc_info 置为 GC_GREY),然后对当前 value 的成员进行深度优先遍历,把成员 value 的 refcount 减 1,并且也标为灰色;

(2) 重复遍历缓冲区链表,检查当前 value 引用是否为 0,为 0 则表示确实是垃圾,把它标为白色 (GC_WHITE),如果不为 0 则排除了引用全部来自自身成员的可能,表示还有外部的引用,并不是垃圾,这时候因为步骤 (1) 对成员进行了 refcount 减 1 操作,需要再还原回去,对所有成员进行深度遍历,把成员 refcount 加 1,同时标为黑色;

(3) 再次遍历缓冲区链表,将非 GC_WHITE 的节点从 roots 链表中移出,最终 roots 链表中全部为真正的垃圾,最后将这些垃圾清除。

来自:https://learnku.com/articles/33451

猜你喜欢

转载自blog.csdn.net/jayxujia123/article/details/115049850