1 引用计数
引用计数是指在value中增加一个字段refcount
记录指向当前value的数量,变量复制、函数传参时并不直接硬拷贝一份value数据,而是将refcount++
,变量销毁时将refcount--
,等到refcount
减为0时表示已经没有变量引用这个value,将它销毁即可。
存储结构如下:
1 typedef struct _zend_refcounted_h { 2 uint32_t refcount; /* reference counter 32-bit */ 3 union { 4 struct { 5 ZEND_ENDIAN_LOHI_3( 6 zend_uchar type, 7 zend_uchar flags, /* used for strings & objects */ 8 uint16_t gc_info) /* keeps GC root number (or 0) and color */ 9 } v; 10 uint32_t type_info; 11 } u; 12 } zend_refcounted_h;
引用计数的信息位于给具体value结构的gc中,例如在字符串变量中:
1 typedef struct _zend_string zend_string; 2 struct _zend_string { 3 zend_refcounted_h gc; 4 zend_ulong h; /* hash value */ 5 size_t len; 6 char val[1]; 7 };
2 写时复制
引用计数,多个变量可能指向同一个value,然后通过refcount统计引用数,这时候如果其中一个变量试图更改value的内容则会重新拷贝一份value修改,同时断开旧的指向,写时复制的机制在计算机系统中有非常广的应用,它只有在必要的时候(写)才会发生硬拷贝,可以很好的提高效率
不是所有类型都可以copy的,比如对象、资源,实时上只有string、array两种支持,与引用计数相同,也是通过zval.u1.type_flag
标识value是否可复制的
3 变量回收
PHP变量的回收主要有两种:主动销毁、自动销毁。主动销毁指的就是 unset。自动销毁就是PHP的自动管理机制,在return时减掉局部变量的refcount,即使没有显式的return,PHP也会自动给加上这个操作,另外一个就是写时复制时会断开原来value的指向,这时候也会检查断开后旧value的refcount。
4 垃圾回收
PHP变量的回收是根据refcount实现的,当unset、return时会将变量的引用计数减掉,如果refcount减到0则直接释放value,这是变量的简单gc过程
但是实际过程中出现gc无法回收导致内存泄漏的bug,先看下一个例子:
1 $a = [1]; 2 $a[] = &$a; 3 unset($a);
可以看到,unset($a)
之后由于数组中有子元素指向$a
,所以refcount > 0
,无法通过简单的gc机制回收,这种变量就是垃圾,垃圾回收器要处理的就是这种情况,目前垃圾只会出现在array、object两种类型中,所以只会针对这两种情况作特殊处理:当销毁一个变量时,如果发现减掉refcount后仍然大于0,且类型是IS_ARRAY、IS_OBJECT则将此value放入gc可能垃圾双向链表中,等这个链表达到一定数量后启动检查程序将所有变量检查一遍,如果确定是垃圾则销毁释放。