(九)PHP引用计数和垃圾回收机制详解

PHP版本 :7.1

在学习引用计数之前,需要先了解zval容器的具体定义和结构,在(四)php7zval源码解读这一个节中提到过_zval_struct结构体中的value变量,value变量也是一个结构体类型,该结构体中的不同成员变量存储着该zval容器代表的不同类型的值,其中一个变量就是ref,即引用类型,这就是这篇博文的主题之一。 以及在(五)PHP7 字符串源码解读这一节中提到过字符串中结构体_zend_string中的gc变量是用于引用计数和垃圾回收的,gc变量也是一个结构体类型,该结构体中有个变量refcount就是这篇博文的主题之二。
可以在源码/Zend/zend_types.h中看到,不只是_zend_string结构体,_zend_array、_zend_object结构体等有都一个成员变量gc是zend_refcounted_h类型的。

gc结构

像数组、字符串、对象、引用这些类型,其结构体定义头部都有一个gc变量,其中维护着一个引用计数,gc结构体定义如下:

typedef struct _zend_refcounted_h {
    uint32_t         refcount;          /* 32bit长度的引用计数值 */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,     /* 同zval中的u1.v.type, 记录当前元素的类型 */
                zend_uchar    flags,    /* 标记数组、对象、字符串的类型 */
                uint16_t      gc_info)  /* 高地址用于标记当前元素的颜色(黑,白,灰,紫),低地址标记元素在垃圾回收池中的位置 */
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;
struct _zend_string {
	zend_refcounted_h gc;
	zend_ulong        h;                /* hash value */
	size_t            len;
	char              val[1];
};

struct _zend_resource {
	zend_refcounted_h gc;
	int               handle; // TODO: may be removed ???
	int               type;
	void             *ptr;
};

struct _zend_reference {
	zend_refcounted_h gc;
	zval              val;
};

struct _zend_ast_ref {
	zend_refcounted_h gc;
	zend_ast         *ast;
};
//等等。。。

_zend_refcounted_h 结构体图解如下:
(九)PHP引用计数和垃圾回收机制详解_第1张图片

引用计数

在(五)PHP7 字符串源码解读中已经演示过了字符串变量的refcount值的变化,这里再举一个简单的例子温习一下引用计数:


$a = "hi".time();
echo $a;
$b = $a;
echo $a;

unset($a);
echo $a;
echo $b;

(九)PHP引用计数和垃圾回收机制详解_第2张图片
//第一个echo $a
(九)PHP引用计数和垃圾回收机制详解_第3张图片
//第二个echo $a
(九)PHP引用计数和垃圾回收机制详解_第4张图片
//第三个echo $a,由于unset( $ a)操作,此时u1.v.type值变成了0(IS_UNDEF 未定义)
(九)PHP引用计数和垃圾回收机制详解_第5张图片
//echo $b,refcount又减到了1
(九)PHP引用计数和垃圾回收机制详解_第6张图片
补图,在第三个echo $a处,虽然进行过unset( $ a)操作,只是将 $ a变量设置为了未定义,但是value.str指向的地址将refcount减1,而不是回收了那个内存,可以看出依旧可以取到str的值:
(九)PHP引用计数和垃圾回收机制详解_第7张图片

循环引用



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

unset($a)操作之前,refcount值为2,u1.v.type值为10(引用类型):
(九)PHP引用计数和垃圾回收机制详解_第8张图片
(九)PHP引用计数和垃圾回收机制详解_第9张图片

unset($a)操作之后,refcount值为1,u1.v.type值为0(未定义):
(九)PHP引用计数和垃圾回收机制详解_第10张图片
(九)PHP引用计数和垃圾回收机制详解_第11张图片

垃圾回收

在php5.3之前的版本的垃圾回收机制
(1)当对象被引用时,refcount计数器+1
(2)当unset($a)或者出现写时复制等操作后,计数器-1
(3)PHP会根据refcount的值来判断是不是垃圾,如果refcount值减为0,PHP会将该zval容器当做垃圾释放掉,这种回收机制对于环状引用的变量无法回收。

因此在php5.3及其之后的版本,垃圾回收的机制为
$a的zval的u1.v.type被标记为0(IS_UNDEF),但是refcount大于0的时候,其指向的内存以及该内存指向的其他内存都可能成为垃圾,垃圾收集器会将这部分可能是垃圾的数据收集到缓冲区。垃圾收集要求数据类型是数组或对象。

当unset($a)操作后,如果refcount大于0,则a变量会被插入到垃圾收集缓冲区中。当缓冲区满了,再收集到新元素就会触发垃圾回收算法:

  1. 对所有垃圾收集器收集到的变量进行遍历,将每个元素中gc_info为紫色(垃圾收集器收集到的变量初始颜色为紫色)的改为灰色,引用计数减1;
  2. 扫描所有灰色的元素,如果引用计数任然大于0,说明这个元素还在其他地方使用,那么将其颜色标记为黑色,并将引用计数加1(因为第1步减了1);如果引用计数为0,则将其标记为白色;
  3. 扫描所有元素,将gc_info为黑色的元素从垃圾收集器中移除。然后对白色元素的引用计数加1(因为第1步减了)。将垃圾收集器中的所有元素移动到待释放的列表中(to_free);
  4. 释放to_free列表中的元素。

你可能感兴趣的:(#,PHP源码学习)