浅析PHP7 的垃圾回收机制


Posted in PHP onSeptember 06, 2019

垃圾回收机制

垃圾回收机制是一种动态存储分配方案。它会自动释放程序不再需要的已分配的内存块。 自动回收内存的过程叫垃圾收集。垃圾回收机制可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻辑。 在现在的流行各种语言当中,垃圾回收机制是新一代语言所共有的特征。

垃圾的产生

PHP7 中复杂类型,像字符串、数组、对象等的数据结构中,头部都有一个 gc, 这个 gc 的作用就是用来对垃圾回收的支持。当变量赋值、传递时,会增加 value 的引用数, unset、return 等释放变量时再减掉引用数,减掉后如果发现 refcount 变为 0 则直接释放 value,这是变量的基本回收过程。

不过有一种问题是这个机制无法解决的,就是循环引用的问题。

什么是循环引用呢? 简单说就是变量的内部里存的 value 又引用了变量自身。 这种比较经常发生在数组和对象类型的变量上。

这里先讲一下引用,即 zend_reference 这个类型,这个是 PHP7 新增的变量类型,当对变量使用 “&” 操作时,会创建新的中间结构体 zend_reference,这个结构体会真正的指向对应的 value 结构。

举个例子:

// 当进行如下赋值操作时
$a = 'hello'; // $a -> zend_string
$b = $a; // $b,$a -> zend_string
$c = &$b; // $c,$b -> zval(type = IS_REFERENCE, refcount = 2) -> zend_string

最终会变成如下这样:

 浅析PHP7 的垃圾回收机制

即 $b 和 $c 的 zval 是通过中间结构体 zend_reference 再指向最终的 zend_string

回到循环引用的问题,举个数组循环引用例子:

$arr = [1];
$a[] = &$a;
unset($a);

使用 & 操作之后,变量 a 就变成了引用类型且引用计数 refcount 为 2,而又赋值给自己里面的元素,即变量 a 变成了自己引用自己。

具体如下如所示:

 浅析PHP7 的垃圾回收机制

当 unset 之后就变成下图这样:

 浅析PHP7 的垃圾回收机制

即 $a 所在的 zval 类型已经变成了 IS_UNDEF 了,zend_reference 结构体的引用计数减 1,但是仍然大于 0,这时候,这部分结构体就变成了垃圾,对此不处理的话,就可能会造成内存泄露。这里就需要垃圾收集器将这部分收集到缓冲区,之后进行回收处理。

回收过程

如果当变量的 refcount 减小后大于 0,PHP 并不会立即对这个变量进行垃圾鉴定和回收,而是放入一个缓冲区中,等这个缓冲区满了以后(10000 个值)再统一进行处理,加入缓冲区的是变量 zend_value 里的 gc,目前垃圾只会出现在数组和对象两种类型中,数组的情况上面已经介绍了,对象的情况则是成员属性引用对象本身导致的,其它类型不会出现这种变量中的成员引用变量自身的情况,所以垃圾回收只会处理这两种类型的变量。

gc 的结构 zend_refcounted_h 具体如下:

typedef struct _zend_refcounted_h {
  uint32_t     refcount; // 记录 zend_value 的引用数
  union {
    struct {
      zend_uchar  type, // zend_value的类型, 与zval.u1.type一致
      zend_uchar  flags, 
      uint16_t   gc_info // GC信息,记录在 gc 池中的位置和颜色,垃圾回收的过程会用到
    } v;
    uint32_t type_info;
  } u;
} zend_refcounted_h;

一个变量只能加入一次缓冲区,为了防止重复加入,变量加入后会把 zend_refcounted_h.gc_info 置为 GC_PURPLE,即标为紫色,后续不会重复插入。

垃圾缓冲区是一个双向链表,等到缓存区满了以后则启动垃圾检查过程:遍历缓冲区,对当前变量的所有成员进行遍历,然后把成员的 refcount 减 1 (如果成员还包含子成员则也进行递归遍历,即深度优先遍历),最后再检查当前变量的引用,如果减为了 0 则为垃圾。这个算法的原理核心是:垃圾是由于成员引用自身导致的,那么就对所有的成员减一遍引用,如果发现最后变量本身的 refcount 变为了 0 则就表明其引用全部来自自身成员,即其他任何地方都不再使用它,那么它就是垃圾,需要被回收掉。反之说明不是垃圾,需要将其从缓冲区移出去。具体的过程如下:

(1) 从缓冲区链表的 roots 开始遍历,把当前 value 标为灰色 (zend_refcounted_h.gc_info 置为 GC_GREY),然后对当前 value 的成员进行深度优先遍历,把成员 value 的 refcount 减 1,并且也标为灰色;

(2) 重复遍历缓冲区链表,检查当前 value 引用是否为 0,为 0 则表示确实是垃圾,把它标为白色(GC_WHITE),如果不为 0 则排除了引用全部来自自身成员的可能,表示还有外部的引用,并不是垃圾,这时候因为步骤(1)对成员进行了 refcount 减 1 操作,需要再还原回去,对所有成员进行深度遍历,把成员 refcount 加 1,同时标为黑色;

(3) 再次遍历缓冲区链表,将非 GC_WHITE 的节点从 roots 链表中移出(移到待释放的列表),最终 roots 链表中全部为真正的垃圾,最后将这些垃圾清除。

总结

以上所述是小编给大家介绍的PHP7 的垃圾回收机制,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

PHP 相关文章推荐
PHP与已存在的Java应用程序集成
Oct 09 PHP
php URL验证正则表达式
Jul 19 PHP
PHP 验证码不显示只有一个小红叉的解决方法
Sep 30 PHP
linux下安装php的memcached客户端
Aug 03 PHP
PHP自动生成表单代码分享
Jun 19 PHP
PHP闭包函数传参及使用外部变量的方法
Mar 15 PHP
老司机传授Ubuntu下Apache+PHP+MySQL环境搭建攻略
Mar 20 PHP
Yii2使用swiftmailer发送邮件的方法
May 03 PHP
PHP读取大文件末尾N行的高效方法推荐
Jun 03 PHP
phpinfo的知识点总结
Oct 10 PHP
PHP getID3类的使用方法学习笔记【附getID3源码下载】
Oct 18 PHP
PHP实现rar解压读取扩展包小结
Jun 03 PHP
PHP的介绍以及优势详细分析
Sep 05 #PHP
laravel5.1框架基础之Blade模板继承简单使用方法分析
Sep 05 #PHP
Laravel5.4框架中视图共享数据的方法详解
Sep 05 #PHP
php生成微信红包数组的方法
Sep 05 #PHP
Laravel框架创建路由的方法详解
Sep 04 #PHP
Laravel框架查询构造器 CURD操作示例
Sep 04 #PHP
Yii框架操作cookie与session的方法实例详解
Sep 04 #PHP
You might like
深入解析fsockopen与pfsockopen的区别
2013/07/05 PHP
thinkphp3.2.3版本的数据库增删改查实现代码
2016/09/22 PHP
PHP中字符串长度的截取用法示例
2017/01/12 PHP
PHP5.6新增加的可变函数参数用法分析
2017/08/25 PHP
php PDO属性设置与操作方法分析
2018/12/27 PHP
PHP正则之正向预查与反向预查讲解与实例
2020/04/06 PHP
详解jQuery插件开发中的extend方法
2013/11/19 Javascript
关于JavaScript对象的动态选择及遍历对象
2014/03/10 Javascript
吐槽一下我所了解的Node.js
2014/10/08 Javascript
js封装可使用的构造函数继承用法分析
2015/01/28 Javascript
基于javascript实现句子翻牌网页版小游戏
2016/03/23 Javascript
AngularJS动态生成div的ID源码解析
2016/08/29 Javascript
jQuery插件HighCharts绘制2D柱状图、折线图的组合双轴图效果示例【附demo源码下载】
2017/03/09 Javascript
移动端利用H5实现压缩图片上传功能
2017/03/29 Javascript
Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码
2018/05/21 Javascript
在Vue组件中获取全局的点击事件方法
2018/09/06 Javascript
react同构实践之实现自己的同构模板
2019/03/13 Javascript
Python进程间通信Queue实例解析
2018/01/25 Python
Python Image模块基本图像处理操作小结
2019/04/13 Python
如何将你的应用迁移到Python3的三个步骤
2019/12/22 Python
如何在 Django 模板中输出 "{{"
2020/01/24 Python
对tensorflow中tf.nn.conv1d和layers.conv1d的区别详解
2020/02/11 Python
GAP美国官网:美国休闲时尚品牌
2016/08/26 全球购物
GANT葡萄牙官方商店:拥有美国运动服传统的生活方式品牌
2018/10/18 全球购物
捷克鲜花配送:Florea.cz
2018/10/29 全球购物
大学本科毕业生求职简历的自我评价
2013/10/09 职场文书
应届生服务员求职信
2013/10/31 职场文书
师范学院毕业生求职信范文
2013/12/26 职场文书
护理职业生涯规划书
2014/01/24 职场文书
会计简历自我评价
2015/03/10 职场文书
电子商务专业求职信范文
2015/03/19 职场文书
2015年世界急救日宣传活动方案
2015/05/06 职场文书
田径运动会通讯稿
2015/07/18 职场文书
教你用python控制安卓手机
2021/05/13 Python
ipad隐藏软件app图标方法
2022/04/19 数码科技
Windows Server 修改远程桌面端口的实现
2022/06/25 Servers