当前位置: 首页 > 后端技术 > PHP

PHP7的垃圾回收机制解析

时间:2023-03-29 19:36:17 PHP

垃圾回收机制垃圾回收机制是一种动态的存储分配方案。它会自动释放程序不再需要的已分配内存块。自动回收内存的过程称为垃圾回收。垃圾回收机制让程序员不必太在意程序内存分配,从而可以将更多的精力投入到业务逻辑中。在当前流行的语言中,垃圾回收机制是新一代语言的共同特征。垃圾生成在PHP7中的复杂类型中,字符串、数组、对象等数据结构,头部都有一个gc,这个gc的作用就是支持垃圾回收。当变量被赋值或转移时,该值的引用次数会增加,而当变量被unset、return等释放时,引用次数会减去。减量后,如果refcount变为0,则直接释放该值。这就是变量的基本恢复过程。但是,有一个问题是这个机制无法解决的,就是循环引用的问题。什么是循环引用?简单的说,变量内部存储的值就是指变量本身。这种比较通常发生在数组和对象类型的变量中。这里先说引用,也就是zend_reference类型,它是PHP7中新增的变量类型。当对一个变量使用“&”操作时,会创建一个新的中间结构zend_reference,这个结构实际会指向对应的值结构。例如://$a='你好';//$a->zend_string$b=$a;//$b,$a->zend_string$c=&$b;//$c,$b->zval(type=IS_REFERENCE,refcount=2)->zend_string最终会变成如下:$b和$c的zval通过中间结构zend_reference指向最终的zend_string。回到循环引用的问题,我们以循环数组引用为例:$a=[1];$a[]=&$a;取消设置($a);使用&操作后,变量a变成了引用类型,引用计数refcount为2,赋给了自己内部的元素,即变量a变成了自己的引用。具体如下:unset后变成如下图:即$a所在的zval类型变成了IS_UNDEF,zend_reference结构体的引用计数减1,但还是大于0,这时这部分结构体就变成了垃圾,如果不加以处理,可能会造成内存泄漏。这里需要垃圾收集器将这部分收集到缓冲区中,然后进行回收。在回收过程中,如果变量的refcount减少后大于0,PHP不会立即识别并回收该变量,而是将其放入一个缓冲区,等缓冲区满(10000个值)再统一。为了处理,将变量zend_value中的gc添加到缓冲区中。目前,垃圾只会出现在数组和对象两种类型中。上面介绍了数组的情况,对象的情况是成员属性引用了对象本身造成的。其他没有引用变量本身的变量成员,所以垃圾回收只会处理这两类变量。gczend_refcounted_h的结构如下:typedefstruct_zend_refcounted_h{uint32_trefcount;//记录zend_value的引用次数union{struct{zend_uchartype,//zend_value的类型与zval.u1.type一致zend_ucharflags,uint16_tgc_info//GC信息,记录在gc池中的位置和颜色会在垃圾收集过程中使用}v;uint32_t类型信息;}你;}zend_refcounted_h;一个变量只能被添加到缓冲区一次,为了防止重复添加,添加变量后会添加zend_refcounted_h.gc_info设置为GC_PURPLE,即标记为紫色,不会重复插入未来。垃圾缓冲区是一个双向链表。当缓冲区满时,开始垃圾检查过程:遍历缓冲区,遍历当前变量的所有成员,然后将该成员的refcount减1(如果该成员还包含子成员,也会递归遍历,即深度优先遍历),最后检查当前变量的引用,如果减为0则为垃圾。这个算法的核心原理是:垃圾是由成员引用自己引起的,然后减少对所有成员的引用,如果发现变量本身的refcount变为0,则意味着它的所有引用都来自它自己的成员,也就是在其他任何地方,如果你不再使用它,那么它就是垃圾,需要回收。相反,它不是垃圾,需要从缓冲区中移除。具体过程如下:(1)从buffer链表的根开始遍历,将当前值标记为灰色(设置zend_refcounted_h.gc_info为GC_GREY),然后对当前值的成员进行深度优先遍历,并将成员值的引用计数减少1,并标记为灰色;(2)反复遍历bufferlist,检查当前值引用是否为0,如果为0则说明确实是垃圾,标记为白色(GC_WHITE),如果不为0则排除所有引用来自的可能性它自己的成员意味着有外部引用,这不是垃圾。此时由于步骤(1)将member的refcount减1,需要恢复,需要对所有member进行深度遍历,将memberrefcount加1,并在同时;(3)再次遍历bufferlist,从rootslist中移除非GC_WHITE节点,最后将rootslist中所有真正的垃圾全部清除,最后清除这些垃圾。参考《PHP7 底层设计与源码解析》php7-internal