解析PHP7中的zval结构和采用计数机制

    最近,当我在PHP7中查找有关垃圾回收的信息时,Internet上的一些代码示例在本地环境中运行时显示出不同的结果,这使我很困惑。 仔细考虑后发现问题并不难:这些文章大多数来自PHP5.x时代,并且在PHP7发行之后,采用了新的zval结构,并且相关信息相对较差,因此我合并了 一些信息进行总结,主要集中在解释新的zval容器中的引用计数机制上,希望您能给一些建议。


    PHP7 中新的 zval 结构

    明人不说暗话,先看代码!

解析PHP7中的zval结构和采用计数机制1.png

    对于该结构的详细描述可以参考文末鸟哥的文章,写的非常详细,我就不关公面前耍大刀了,这里我只提出几个比较关键的点:


    PHP7 中的变量分为变量名和变量值两部分,分别对应 zval_struct 和在其中声明的 value


    zval_struct.value 中的 zend_long 、double 都是简单数据类型,能够直接储存具体的值,而其他复杂数据类型储存一个指向其他数据结构的指针


    PHP7 中,引用计数器储存在 value 中而不是 zval_struct


    NULL、布尔型都属于没有值的数据类型(其中布尔型通过 IS_FALSE 和 IS_TRUE 两个常量来标记),自然也就没有引用计数


    引用(REFERENCE)变为了一种数据结构而不再只是一个标记位了,它的结构如下:

解析PHP7中的zval结构和采用计数机制2.png

    zend_reference 作为 zval_struct 中包含的一种 value 类型,也拥有自己的 val 值,这个值是指向一个 zval_struct.value 的。他们都拥有自己的引用计数器。

解析PHP7中的zval结构和采用计数机制3.png

    针对第六点,请看如下代码:

解析PHP7中的zval结构和采用计数机制4.png

    此时的数据结构是这样的:


    $a 与 $b 各拥有一个 zval_struct 容器,并且其中的 value 都指向同一个 zend_reference 结构,zend_reference 内嵌一个 val 结构, 指向同一个 zend_string,字符串的内容就储存在其中。


    而 $c 也拥有一个 zval_struct,而它的 value 在初始化的时候可以直接指向上面提到的 zend_string ,这样在拷贝时就不会产生复制。


    下面我们就聊一聊在这种全新的 zval 结构中,会出现的种种现象,和这些现象背后的原因。


    问题

    一. 为什么某些变量的引用计数器的初始值为 0

    现象

解析PHP7中的zval结构和采用计数机制5.png

    原因

    在 PHP7 中,为一个变量赋值的时候,包含了两部分操作:


    1、为符号量(即变量名)申请一个 zval_struct 结构


    2、将变量的值储存到 zval_struct.value 中 对于 zval 在 value 字段中能保存下的值,就不会在对他们进行引用计数,而是在拷贝的时候直接赋值,这部分类型有:


    IS_LONG

    IS_DOUBLE

    即我们在 PHP 中的整形与浮点型。


    那么 var_str 的 refcount 为什么也是 0 呢?

    这就牵扯到 PHP 中字符串的两种类型:


    1、interned string 内部字符串(函数名、类名、变量名、静态字符串):

解析PHP7中的zval结构和采用计数机制6.png

    2、普通字符串:

解析PHP7中的zval结构和采用计数机制7.png

    对于内部字符串而言,字符串的内容是唯一不变的,相当于 C 语言中定义在静态变量区的字符串,他们的生存周期存在于整个请求期间,request 完成后会统一销毁释放,自然也就无需通过引用计数进行内存管理。


    二. 为什么在对整形、浮点型和静态字符串型变量进行引用赋值时,计数器的值会直接变为2

    现象

解析PHP7中的zval结构和采用计数机制8.png

    原因

    回忆一下我们开头讲的 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中的zval结构和采用计数机制9.png

    原因

    这牵扯到 PHP7 中的另一个概念,叫做 immutable array(不可变数组)。

解析PHP7中的zval结构和采用计数机制10.png

    不可变数组是 opcache 扩展优化出的一种数组类型,简单的说,所有多次编译结果恒定不变的数组,都会被优化为不可变数组,下面是一个反例:

解析PHP7中的zval结构和采用计数机制11.png

    PHP 在编译阶段无法得知 time() 函数的返回值,所以此处的 $array 是可变数组。


    不可变数组与我们上面提到的内部字符串相同。 它们不使用引用计数,但是区别在于内部字符串的计数值始终为0,而不可变数组使用的伪计数值为2。


    总结

    简单数据类型


    整形(不使用引用计数)

    浮点型(不使用引用计数)

    布尔型(不使用引用计数)

    NULL(不使用引用计数)

    复杂数据类型


    字符串


    普通字符串(使用引用计数,初始值为 1)

    内部字符串(不使用引用计数,引用计数值恒为 0)

    数组


    普通数组(使用引用计数,初始值为 1)

    不可变数组(不使用引用计数,使用伪计数值 2)

    对象(使用引用计数,初始值为 1)

对我有帮助
23人认为有帮助

相关帮助