最近,当我在PHP7中查找有关垃圾回收的信息时,Internet上的一些代码示例在本地环境中运行时显示出不同的结果,这使我很困惑。 仔细考虑后发现问题并不难:这些文章大多数来自PHP5.x时代,并且在PHP7发行之后,采用了新的zval结构,并且相关信息相对较差,因此我合并了 一些信息进行总结,主要集中在解释新的zval容器中的引用计数机制上,希望您能给一些建议。
PHP7 中新的 zval 结构
明人不说暗话,先看代码!
对于该结构的详细描述可以参考文末鸟哥的文章,写的非常详细,我就不关公面前耍大刀了,这里我只提出几个比较关键的点:
PHP7 中的变量分为变量名和变量值两部分,分别对应 zval_struct 和在其中声明的 value
zval_struct.value 中的 zend_long 、double 都是简单数据类型,能够直接储存具体的值,而其他复杂数据类型储存一个指向其他数据结构的指针
PHP7 中,引用计数器储存在 value 中而不是 zval_struct
NULL、布尔型都属于没有值的数据类型(其中布尔型通过 IS_FALSE 和 IS_TRUE 两个常量来标记),自然也就没有引用计数
引用(REFERENCE)变为了一种数据结构而不再只是一个标记位了,它的结构如下:
zend_reference 作为 zval_struct 中包含的一种 value 类型,也拥有自己的 val 值,这个值是指向一个 zval_struct.value 的。他们都拥有自己的引用计数器。
针对第六点,请看如下代码:
此时的数据结构是这样的:
$a 与 $b 各拥有一个 zval_struct 容器,并且其中的 value 都指向同一个 zend_reference 结构,zend_reference 内嵌一个 val 结构, 指向同一个 zend_string,字符串的内容就储存在其中。
而 $c 也拥有一个 zval_struct,而它的 value 在初始化的时候可以直接指向上面提到的 zend_string ,这样在拷贝时就不会产生复制。
下面我们就聊一聊在这种全新的 zval 结构中,会出现的种种现象,和这些现象背后的原因。
问题
一. 为什么某些变量的引用计数器的初始值为 0
现象
原因
在 PHP7 中,为一个变量赋值的时候,包含了两部分操作:
1、为符号量(即变量名)申请一个 zval_struct 结构
2、将变量的值储存到 zval_struct.value 中 对于 zval 在 value 字段中能保存下的值,就不会在对他们进行引用计数,而是在拷贝的时候直接赋值,这部分类型有:
IS_LONG
IS_DOUBLE
即我们在 PHP 中的整形与浮点型。
那么 var_str 的 refcount 为什么也是 0 呢?
这就牵扯到 PHP 中字符串的两种类型:
1、interned string 内部字符串(函数名、类名、变量名、静态字符串):
2、普通字符串:
对于内部字符串而言,字符串的内容是唯一不变的,相当于 C 语言中定义在静态变量区的字符串,他们的生存周期存在于整个请求期间,request 完成后会统一销毁释放,自然也就无需通过引用计数进行内存管理。
二. 为什么在对整形、浮点型和静态字符串型变量进行引用赋值时,计数器的值会直接变为2
现象
原因
回忆一下我们开头讲的 zval_struct 中 value 的数据结构,当为一个变量赋整形、浮点型或静态字符串类型的值时,value 的数据类型为 zend_long 、 double 或 zend_string,这时值是可以直接储存在 value 中的。而按值拷贝时,会开辟一个新的 zval_struct 以同样的方式将值储存到相同数据类型的 value 中,所以 refcount 的值一直都会为 0。
但是当使用 & 操作符进行引用拷贝时,情况就不一样了:
PHP 为 & 操作符操作的变量申请一个 zend_reference 结构
将 zend_reference.value 指向原来的 zval_struct.value
zval_struct.value 的数据类型会被修改为 zend_refrence
将 zval_struct.value 指向刚刚申请并初始化后的 zend_reference
为新变量申请 zval_struct 结构,将他的 value 指向刚刚创建的 zend_reference
此时:$var\_int\_1 和 $var_int_2 都拥有一个 zval_struct 结构体,并且他们的 zval_struct.value 都指向了同一个 zend_reference 结构,所以该结构的引用计数器的值为 2。
题外话:zend_reference 又指向了一个整形或浮点型的 value,如果指向的 value 类型是 zend_string,那么该 value 引用计数器的值为 1。而 xdebug 出来的 refcount 显示的是 zend_reference 的计数器值(即 2)
三. 为什么初始数组的引用计数器的值为 2
现象
原因
这牵扯到 PHP7 中的另一个概念,叫做 immutable array(不可变数组)。
不可变数组是 opcache 扩展优化出的一种数组类型,简单的说,所有多次编译结果恒定不变的数组,都会被优化为不可变数组,下面是一个反例:
PHP 在编译阶段无法得知 time() 函数的返回值,所以此处的 $array 是可变数组。
不可变数组与我们上面提到的内部字符串相同。 它们不使用引用计数,但是区别在于内部字符串的计数值始终为0,而不可变数组使用的伪计数值为2。
总结
简单数据类型
整形(不使用引用计数)
浮点型(不使用引用计数)
布尔型(不使用引用计数)
NULL(不使用引用计数)
复杂数据类型
字符串
普通字符串(使用引用计数,初始值为 1)
内部字符串(不使用引用计数,引用计数值恒为 0)
数组
普通数组(使用引用计数,初始值为 1)
不可变数组(不使用引用计数,使用伪计数值 2)
对象(使用引用计数,初始值为 1)
我来说两句