内容简介: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,很好理解:
- 执行关键的一步:$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份,方便管理与维护。
循环引用问题
- 我们首先构造一个循环引用:
<?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。]见下图:
- 利用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的变量,会把它放入一个双向链表中,等待垃圾回收,相当于一个缓冲区的作用。待缓冲区满了之后(100000个存储单元),然后再对其进行标记和清除(以后会在代码层面具体讲垃圾回收的方法)。
- 缓冲区的作用就是减少垃圾回收算法运行的频率,减少对正在运行的服务端代码的影响。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- dubbo源码解析(四十五)服务引用过程
- 苹果iOS系统源码思考:对象的引用计数存储在哪里?--从runtime源码得到的启示
- 强引用、软引用、弱引用、虚引用
- java的强引用、软引用、弱引用、幻象引用,引用队列总结
- Java 对象引用方式 —— 强引用、软引用、弱引用和虚引用
- 强引用、软引用、弱引用、幻象引用有什么区别?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python语言程序设计
[美]梁勇(Lang Y. D.) / 李娜 / 机械工业出版社 / 2015-4 / 79.00元
本书采用“问题驱动”、“基础先行”和“实例和实践相结合”的方式,讲述如何使用Python语言进行程序设计。本书首先介绍Python程序设计的基本概念,接着介绍面向对象程序设计方法,最后介绍算法与数据结构方面的内容。为了帮助学生更好地掌握相关知识,本书每章都包括以下模块:学习目标,引言,关键点,检查点,问题,本章总结,测试题,编程题,注意、提示和警告。 本书可以作为高等院校计算机及相关专业Py......一起来看看 《Python语言程序设计》 这本书的介绍吧!