最近在看PHP垃圾回收机制,顺便热蒸现卖,做下总结:
基础概念:写时拷贝 引用计数 回收周期
php的每个变量都会存储在一个叫做zval的变量容器中,这个容器的定义如下:
typedef struct _zval_struct zval;
//zval定义
struct _zval_struct {
zvalue_value value; /* value*/
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__g
}
//avalue_value定义
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
}zvalue_value;
在php中,存在8中变量类型,可以分为三类:
- 标量类型:bool int float(double) string
- 复合类型:array object
- 特殊类型:resource NULL
在变量声明开始,就会判断数据类型存到zval结构体,结构体的字段解释:
属性名 | 含义 | 属性值 |
refcount_gc | 表示变量引用计数 | 默认1 |
is_ref_gc | 表示是否为引用 | bool类型,默认0,用于区别普通变量和引用变量 |
value | 变量的值 | |
type | 变量的内部类型 | IS_NULL, IS_BOOL,IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_RESOURCE |
介绍完zval结构,我们来说下php是使用变量的时候都做了些什么,我们以php5.6版本和php7.1版本作为比较做以下对比,以此能够看出php不同版本在处理垃圾引用计数上的略微差异:
从以上对比可发现,php5.6和php7.1在变量赋值的时候内存变化是有差异的:
标量:
- php5.6不会分配新的内存中间,而是将改变量的引用计数加1,而php7.1则分配了一块新的内存中间,并且将b指向它,此时有两个相同的内存变量,他们的引用计数都是1.
- php5.6和php7.1对待数组复合变量的时候也是有差异的,当定义一个一维数组变量arr时,例如demo3,php5.6会生成三个变量,arr,a,b,三个变量的引用计数都是1,而php7.1的arr的引用计数是2;
- 当执行$brr=$arr后,php5.6arr引用计数发生变化,而php7.1没有变化;
- 当修改$brr的值例如:$brr[a] = 'ddd',则php5.6的arr和brr引用计数都变为1,此时发生了写时拷贝,而arr的引用计数仍为2,brr引用计数为1.就是说php7使用会保持原数组引用计数为2.
数组在循环引用的过程中会造成内存泄漏,具体请看:点击打开链接,所以引入了周期性清理zval容器的问题,引用计数+周期清理能够将php内存泄漏控制在一定范围。