资讯专栏INFORMATION COLUMN

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

Aomine / 1144人阅读

摘要:中的引用引用可以通过不同的变量名,访问同一个变量内容。在进行引用赋值后,等号左右两边的变量均变成了引用类型。缓冲区的作用就是减少垃圾回收算法运行的频率,减少对正在运行的服务端代码的影响。

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的type变成了10 (IS_REFERENCE)类型,并且ref字段指向了一个新的结构体,这就是zend_reference,zend_reference中存储着$a与$b共同的值1,由于$a与$b同时引用着这个结构体,故此时该结构体的refcount = 2。

接下来打印$b,观察$b的存储情况:

观察上图,发现与$b的type也是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_h gc; //gc相关,存有refcount
    zval  val;   //引用类型的变量值存在这个zval中的zend_value字段中。简单类型的值直接存在这里,复杂类型的值存储对应数据结构的指针,来找到这个变量的值,和之前讲基本变量时候讲过的一样。
};

这个结构体一共只有2个字段,gc字段中是zend_refcounted_gc结构体类型,其中存储了引用计数;val字段存储了引用类型变量的值(简单类型如整型、浮点型的值直接存在这里,复杂类型存对应数据结构的指针,与之前讲基本变量的时候讲过的一样)。这样相当于加了一个中间层,使得原始的zend_string或zend_array在内存中只有1份,方便管理与维护。

循环引用问题

我们首先构造一个循环引用:

 time()];
echo $a;
$a[] = &$a; //循环引用
echo $a;
unset($a);
echo $a;

注意:由于开启opcache的PHP7会在数组初始化的元素全部为常量元素的时候,将其优化成不可变数组(immutable array),这里的引用计数值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字段中的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_reference指向这个zend_array,所以refcount = 1。

接下来继续执行unset($a):

我们可以看到,$a的type类型变成了0(IS_UNDEF),同时其指向的zend_reference结构体的refcount变为了1(因为$a数组中的元素仍然在指向它),我们画图来表示一下现在的内存情况:

那么问题出现了,$a是unset掉了,但是由于原始的zend_array中的元素仍然在指向仍然在指向zend_reference结构体,所以zend_reference的refcount是1,而并非是预期的0。这样一来,这两个zend_reference与zend_array结构在unset($a)之后,仍然存在于内存之中,如果对此不作任何处理,就会造成内存泄漏。

那么如何解决循环引用带来的内存泄漏问题呢?垃圾回收就要派上用场了。在PHP7中,如果检测到refcount -1 后仍 > 0的变量,会把它放入一个双向链表中,等待垃圾回收,相当于一个缓冲区的作用。待缓冲区满了之后(10000个存储单元),然后再对其进行标记和清除(以后会在代码层面具体讲垃圾回收的方法)。

缓冲区的作用就是减少垃圾回收算法运行的频率,减少对正在运行的服务端代码的影响。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/31313.html

相关文章

  • 【LNMPR源码学习】笔记汇总

    摘要:此文用于汇总跟随陈雷老师及团队的视频,学习源码过程中的思考整理与心得体会,此文会不断更新视频传送门每日学习记录使用录像设备记录每天的学习源码学习源码学习内存管理笔记源码学习内存管理笔记源码学习内存管理笔记源码学习基本变量笔记 此文用于汇总跟随陈雷老师及团队的视频,学习源码过程中的思考、整理与心得体会,此文会不断更新 视频传送门:【每日学习记录】使用录像设备记录每天的学习 PHP7...

    Barrior 评论0 收藏0
  • PHP源码学习2019-04-01 PHP垃圾回收1

    摘要:以上详细的讲解请看源码学习引用那么如何解决循环引用带来的内存泄漏问题呢我们的垃圾回收就要派上用场了。接下来判断如果垃圾回收器已经运行,那么本次就不再执行了。 baiyan 全部视频:https://segmentfault.com/a/11... 垃圾回收触发条件 我们知道,在PHP中,如果一个变量的引用计数减少到0(没有任何地方在使用这个变量),它所占用的内存就会被PHP虚拟机自动回...

    Leo_chen 评论0 收藏0
  • PHP源码学习2019-03-12 PHP基本变量笔记

    摘要:中以表示所有的变量,它是一个结构体。类型是一个联合体,共占用。字段表示字符串的哈希值,在数组的中有用,方便快速定位。字段表示字符串长度这里就是一个柔性数组,在等源码中也被大量使用。 baiyan 全部视频:https://segmentfault.com/a/11... 源视频地址:http://replay.xesv5.com/ll/24... 引入及基本概念 变量本质上就是给一段...

    Fundebug 评论0 收藏0

发表评论

0条评论

Aomine

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<