如果当变量的refcount减少后大于0,PHP并不会立即进行对这个变量进行垃圾鉴定,而是放入一个缓冲buffer中,等这个buffer满了以后(10000个值)再统一进行处理,加入buffer的是变量zend_value的zend_refcounted_h
:
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信息,垃圾回收的过程会用到
} v;
uint32_t type_info;
} u;
} zend_refcounted_h;
一个变量只能加入一次buffer,为了防止重复加入,变量加入后会把zend_refcounted_h.gc_info
置为GC_PURPLE
,即标为紫色,下次refcount减少时如果发现已经加入过了则不再重复插入。垃圾缓存区是一个双向链表,等到缓存区满了以后则启动垃圾检查过程:遍历缓存区,再对当前变量的所有成员进行遍历,然后把成员的refcount减1(如果成员还包含子成员则也进行递归遍历,其实就是深度优先的遍历),最后再检查当前变量的引用,如果减为了0则为垃圾。这个算法的原理很简单,垃圾是由于成员引用自身导致的,那么就对所有的成员减一遍引用,结果如果发现变量本身refcount变为了0则就表明其引用全部来自自身成员。具体的过程如下:
(1) 从buffer链表的roots开始遍历,把当前value标为灰色(zend_refcounted_h.gc_info置为GC_GREY),然后对当前value的成员进行深度优先遍历,把成员value的refcount减1,并且也标为灰色;
(2) 重复遍历buffer链表,检查当前value引用是否为0,为0则表示确实是垃圾,把它标为白色(GC_WHITE),如果不为0则排除了引用全部来自自身成员的可能,表示还有外部的引用,并不是垃圾,这时候因为步骤(1)对成员进行了refcount减1操作,需要再还原回去,对所有成员进行深度遍历,把成员refcount加1,同时标为黑色;
(3) 再次遍历buffer链表,将非GC_WHITE的节点从roots链表中删除,最终roots链表中全部为真正的垃圾,最后将这些垃圾清除。