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

栏目: PHP · 发布时间: 4年前

内容简介:baiyan全部视频:原视频地址:

【PHP源码学习】2019-03-19 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,很好理解:

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

  • 执行关键的一步:$b = &$a,打印$a的值,观察$a的存储情况:

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

  • 观察上图,可以发现$a的type变成了10 (IS_REFERENCE)类型,并且ref字段指向了一个新的结构体,叫做zend_reference,zend_reference中存储着$a与$b共同的值1,由于$a与$b同时引用着这个结构体,故此时该结构体的refcount = 2。
  • 接下来打印$b,观察$b的存储情况:

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

  • 观察上图,发现与$b的type也是IS_REFERENCE类型,且ref字段也指向了一个新的zend_reference结构体,比较$a与$b指向的zend_reference,二者地址相同,说明指向了同一个zend_reference结构体。此时两个变量的存储情况如下图所示:

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

  • 接下来执行unset($b),观察$a以及zend_reference的存储情况,我们看是否符合预期:

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

  • 我们看到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份,方便管理与维护。

循环引用问题

  • 我们首先构造一个循环引用:
<?php
$a = ['time' => time()];
echo $a;
$a[] = &$a; //循环引用
echo $a;
unset($a);
echo $a;
  • 注意:由于开启opcache的PHP7会在数组初始化的元素全部为常量元素的时候,将其优化成不可变数组(immutable array),这里的引用计数值refcount = 2只是一个伪引用计数,所以我们使用$a = ['time' => time()],让其初始化后的refcount为正常的1。]见下图:

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

  • 利用gdb调试这段代码:
  • 执行完$a初始化并打印$a,refcount为1,type为7(IS_ARRAY)而此时的ref字段中的值是非法地址,说明此时还没有生成中间的zend_reference结构体:

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

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

  • 继续执行下一行$a[] = &$a; 观察下图中绿色方框的含义:

    - $a的zval中的ref指向zend_reference结构体
    - zend_reference结构体中的zval字段中的arr指针指向了原始的zend_array
    - zend_array中的arData指针指向了bucket类型
    - zend_array中的bucket数组元素也是一个IS_REFERENCE类型,它又指回到同一个zend_reference结构体:

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

  • 根据gdb调试情况画出内存结构图:

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

  • 由于zend_reference有两个东西指向它(一个是$a,一个是$a数组中的一个元素),所以refcount = 2。原始的zend_array中也有一个refcount字段,由于只有一个zend_reference指向这个zend_array,所以refcount = 1。
  • 接下来继续执行unset($a):

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

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

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

  • 那么问题出现了,$a是unset掉了,但是由于原始的zend_array中的元素仍然在指向仍然在指向zend_reference结构体,所以zend_reference的refcount是1,而并非是预期的0。这样一来,这两个zend_reference与zend_array结构在unset($a)之后,仍然存在于内存之中,如果对此不作任何处理,就会造成内存泄漏。
  • 那么如何解决循环引用带来的内存泄漏问题呢? 垃圾回收 就要派上用场了。在PHP7中,如果检测到refcount -1 后仍 > 0的变量,会把它放入一个双向链表中,等待垃圾回收,相当于一个缓冲区的作用。待缓冲区满了之后(100000个存储单元),然后再对其进行标记和清除(以后会在代码层面具体讲垃圾回收的方法)。
  • 缓冲区的作用就是减少垃圾回收算法运行的频率,减少对正在运行的服务端代码的影响。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Is Parallel Programming Hard, And, If So, What Can You Do About

Is Parallel Programming Hard, And, If So, What Can You Do About

Paul E. McKenney

The purpose of this book is to help you understand how to program shared-memory parallel machines without risking your sanity.1 By describing the algorithms and designs that have worked well in the pa......一起来看看 《Is Parallel Programming Hard, And, If So, What Can You Do About 》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具