深入理解 PHP7 中全新的 zval 容器和引用计数机制
最近在查阅PHP7垃圾回收的资料的时候,网上的一些代码示例在本地环境下运行时出现了不同的结果,使我一度非常迷惑。仔细一想不难发现问题所在:这些文章大多是PHP5.x时代的,而PHP7发布后,采用了新的zval结构,相关的资料也比较贫瘠,所以我结合一些资料做了一个总结,主要侧重于解释新zval容器中的引用计数机制,如有谬误,还望不吝指教。
PHP7中新的zval结构
明人不说暗话,先看代码!
struct_zval_struct{ union{ zend_longlval;/*longvalue*/ doubledval;/*doublevalue*/ zend_refcounted*counted; zend_string*str; zend_array*arr; zend_object*obj; zend_resource*res; zend_reference*ref; zend_ast_ref*ast; zval*zv; void*ptr; zend_class_entry*ce; zend_function*func; struct{ uint32_tw1; uint32_tw2; }ww; }value; union{ struct{ ZEND_ENDIAN_LOHI_4( zend_uchartype,/*activetype*/ zend_uchartype_flags, zend_ucharconst_flags, zend_ucharreserved)/*callinfoforEX(This)*/ }v; uint32_ttype_info; }u1; union{ uint32_tvar_flags; uint32_tnext;/*hashcollisionchain*/ uint32_tcache_slot;/*literalcacheslot*/ uint32_tlineno;/*linenumber(forastnodes)*/ uint32_tnum_args;/*argumentsnumberforEX(This)*/ uint32_tfe_pos;/*foreachposition*/ uint32_tfe_iter_idx;/*foreachiteratorindex*/ }u2; };
对于该结构的详细描述可以参考文末鸟哥的文章,写的非常详细,我就不关公面前耍大刀了,这里我只提出几个比较关键的点:
- PHP7中的变量分为变量名和变量值两部分,分别对应zval_struct和在其中声明的value
- zval_struct.value中的zend_long、double都是简单数据类型,能够直接储存具体的值,而其他复杂数据类型储存一个指向其他数据结构的指针
- PHP7中,引用计数器储存在value中而不是zval_struct
- NULL、布尔型都属于没有值的数据类型(其中布尔型通过IS_FALSE和IS_TRUE两个常量来标记),自然也就没有引用计数
- 引用(REFERENCE)变为了一种数据结构而不再只是一个标记位了,它的结构如下:
struct_zend_reference{ zend_refcounted_hgc; zvalval; }
6.zend_reference作为zval_struct中包含的一种value类型,也拥有自己的val值,这个值是指向一个zval_struct.value的。他们都拥有自己的引用计数器。
引用计数器用来记录当前有多少zval指向同一个zend_value。
针对第六点,请看如下代码:
$a='foo'; $b=&$a; $c=$a;
此时的数据结构是这样的:
$a与$b各拥有一个zval_struct容器,并且其中的value都指向同一个zend_reference结构,zend_reference内嵌一个val结构,指向同一个zend_string,字符串的内容就储存在其中。
而$c也拥有一个zval_struct,而它的value在初始化的时候可以直接指向上面提到的zend_string,这样在拷贝时就不会产生复制。
下面我们就聊一聊在这种全新的zval结构中,会出现的种种现象,和这些现象背后的原因。
问题
一.为什么某些变量的引用计数器的初始值为0
现象
$var_int=233; $var_float=233.3; $var_str='233'; xdebug_debug_zval('var_int'); xdebug_debug_zval('var_float'); xdebug_debug_zval('var_str'); /**输出** var_int: (refcount=0,is_ref=0)int233 var_float: (refcount=0,is_ref=0)float233.3 var_str: (refcount=0,is_ref=0)string'233'(length=3) **********/
原因
在PHP7中,为一个变量赋值的时候,包含了两部分操作:
- 为符号量(即变量名)申请一个zval_struct结构
- 将变量的值储存到zval_struct.value中对于zval在value字段中能保存下的值,就不会在对他们进行引用计数,而是在拷贝的时候直接赋值,这部分类型有:
- IS_LONG
- IS_DOUBLE
即我们在PHP中的整形与浮点型。
那么var_str的refcount为什么也是0呢?
这就牵扯到PHP中字符串的两种类型:
1.internedstring内部字符串(函数名、类名、变量名、静态字符串):
$str='233'; //静态字符串
2.普通字符串:
$str='233'.time();
对于内部字符串而言,字符串的内容是唯一不变的,相当于C语言中定义在静态变量区的字符串,他们的生存周期存在于整个请求期间,request完成后会统一销毁释放,自然也就无需通过引用计数进行内存管理。
二.为什么在对整形、浮点型和静态字符串型变量进行引用赋值时,计数器的值会直接变为2
现象
$var_int_1=233; $var_int_2=&var_int; xdebug_debug_zval('var_int_1'); /**输出** var_int: (refcount=2,is_ref=1)int233 **********/
原因
回忆一下我们开头讲的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_2都拥有一个zval_struct结构体,并且他们的zval_struct.value都指向了同一个zend_reference结构,所以该结构的引用计数器的值为2。
题外话:zend_reference又指向了一个整形或浮点型的value,如果指向的value类型是zend_string,那么该value引用计数器的值为1。而xdebug出来的refcount显示的是zend_reference的计数器值(即2)
三.为什么初始数组的引用计数器的值为2
现象
$var_empty_arr=[1,2,'3']; xdebug_debug_zval('var_empty_arr'); /**输出** var_arr: (refcount=3,is_ref=0) array(size=3) 0=>(refcount=0,is_ref=0)int1 1=>(refcount=0,is_ref=0)int2 2=>(refcount=1,is_ref=0)string'3'(length=1) **********/
原因
这牵扯到PHP7中的另一个概念,叫做immutablearray(不可变数组)。关于immutablearray的详细介绍我放到下篇文章中讲,这里我们只需要知道,这样定义的数组,叫做不可变数组。
Forarraysthenot-refcountedvariantiscalledan"immutablearray".Ifyouuseopcache,thenconstantarrayliteralsinyourcodewillbeconvertedintoimmutablearrays.Onceagain,theseliveinsharedmemoryandassuchmustnotuserefcounting.Immutablearrayshaveadummyrefcountof2,asitallowsustooptimizecertainseparationpaths.
不可变数组和我们上面讲到的内部字符串一样,都是不使用引用计数的,但是不同点是,内部字符串的计数值恒为0,而不可变数组会使用一个伪计数值2。
总结
以上所述是小编给大家介绍的PHP7中全新的zval容器和引用计数机制,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!