当前位置: 首页 > 后端技术 > PHP

【PHP7源码学习】2019-03-19PHP引用

时间:2023-03-29 14:53:18 PHP

baiyan所有视频:https://segmentfault.com/a/11...原视频地址:http://replay.xesv5.com/ll/26...由于这一系列视频后面会详细讲垃圾回收,所以今天先回顾一下PHP中的参考资料,为以后做铺垫。后续笔记会详细讲解垃圾收集器的相关工作原理。PHP7中的引用:可以通过不同的变量名访问相同的变量内容。PHP7中的引用通过让两个变量指向同一个内存空间来实现上述特性。引用赋值后,等号左右两边的变量就变成了引用类型(IS_REFERENCE)。这个公共内存空间是PHP7专门为引用类型的变量创建的一个结构体,叫做zend_reference。代码示例:$a=1;echo$a;$b=&$a;//$b是对$a的引用echo$a;echo$b;unset($b);echo$a;我们用gdb调试上面的代码:先执行$a=1;并打印$a的值,$a是一个普通的zval,它的类型是IS_LONG,很容易理解:执行一个关键步骤:$b=&$a,打印$a的值,观察存储$a:观察上图,可以发现$a的类型变成了10(IS_REFERENCE)类型,ref域指向了一个新的结构体,即zend_reference,里面存放了$a和$的共同值b为1,由于$a和$b同时引用了这个结构体,此时这个结构体的refcount=2。接下来打印$b,观察$b的存储:观察上图,发现即$b的类型也是IS_REFERENCE类型,ref字段也指向一个zend_reference结构体,比较$a和$b指向的zend_reference,两者地址相同,说明指向同一个zend_reference结构。此时两个变量的存储如下图所示:接下来执行unset($b),观察$a和zend_reference的存储,是否符合预期:我们看到unset($b)后,$a的值zend_reference指向的refcount从2变为1,说明现在只有$a引用这个结构,b不再引用这个结构,其类型变为IS_UNDEF类型,代码执行完毕.然后我们看一下zend_reference结构体的基本结构:struct_zend_reference{zend_refcounted_hgc;//gc相关,有refcountzvalval;//引用类型的变量值存放在这个zval中的zend_value字段中。简单类型的值直接存储在这里,复杂类型的值存储指向相应数据结构的指针,以找到这个变量的值,这和讲基本变量时说的一样。};这个结构一共只有2个字段,gc字段是zend_refcounted_gc结构类型,存放的是引用计数;val字段存储的是引用类型变量的值(整数、浮点数等简单类型直接存储在这里,复杂类型存储的是对应数据结构的指针,和我说的时候一样谈论基本变量)。这相当于加了一个中间层,让原来的zend_string或者zend_array在内存中只有一份,方便管理和维护。循环引用问题我们首先构造一个循环引用:time()];echo$a;$a[]=&$a;//循环引用echo$a;unset($a);echo$a;注意:由于启用opcache的PHP7会在数组中初始化的所有元素都是常量元素时,将数组优化为不可变数组,所以这里的引用计数值refcount=2只是伪引用计数,所以我们使用$a=['time'=>time()]让它初始化的refcount正常为1。]见下图:用gdb调试这段代码:执行完$a初始化,打印$a后,refcount为1,type为7(IS_ARRAY),此时ref域中的值为非法地址,说明没有生成中间zend_reference结构:继续执行下一行$a[]=&$a;观察下图中绿框的含义:-$a的zval中的ref指向zend_reference结构体-zend_reference结构体中的zval字段zend_array中的arr指针指向原来的zend_array-zend_array中的arData指针指向bucket类型——zend_array中的bucket数组元素也是一个IS_REFERENCE类型,指向相同的zend_reference结构:根据gdb调试情况绘制内存结构图:由于有两个东西指向zend_reference结构(一个是$a,另一个是$a数组中的一个元素),所以refcount=2。原来的zend_array中也有一个refcount字段。由于指向这个zend_array的zend_reference只有一个,refcount=1。接下来继续执行unset($a):我们可以看到$a的type变成了0(IS_UNDEF),它指向的zend_reference结构体的refcountto变为1(因为$a数组中的元素还在指向它),我们画图表示当前的内存情况:那么问题来了,$a是unset的,但是因为原来zend_array中的元素是仍然指向zend_reference结构,zend_reference的refcount是1,而不是预期的0。这样zend_reference和zend_array这两个结构在unset($a)之后仍然存在于内存中,如果什么都不做,它会造成内存泄漏。那么如何解决循环引用带来的内存泄漏问题呢?垃圾收集即将派上用场。在PHP7中,如果检测到一个变量在refcount-1后仍然>0,则将其放入双向链表等待垃圾回收,相当于一个buffer。缓冲区满(10000个存储单元)后,会进行标记和清除(垃圾回收的方法以后会在代码层面具体讨论)。缓冲区的作用是降低垃圾回收算法的运行频率,减少对运行服务器代码的影响。