PHP线程的内存回收问题


Posted in PHP onJuly 08, 2016

当一个PHP线程结束时,当前占用的所有内存空间都会被销毁。那么如果这个线程不结束,怎么回收内存呢?

refcount:引用技术器,可以理解为指向该个容器的指针个数吧。

is_ref:是否被引用(只可能是0或者1)

赋值的流程:

<?php
$a = 'aa';
xdebug_debug_zval(a); //(refcount=1, is_ref=0),string 'aa' (length=6)
$b = $a; 
//以下的两个其实是一个变量容器
xdebug_debug_zval(a); //(refcount=2, is_ref=0),string 'aa' (length=6)
xdebug_debug_zval(b); //(refcount=2, is_ref=0),string 'aa' (length=6)
unset($b); //对变量容器 refcount 减1
xdebug_debug_zval(a); //(refcount=1, is_ref=0),string 'aa' (length=6)
xdebug_debug_zval(b); //b: no such symbol b变量被销毁,指向被断掉,如果对应容器的引用技术为零,那么该块儿内存被回收
$b = $a;
$b = 'bb';
xdebug_debug_zval(a); //(refcount=1, is_ref=0),string 'aa' (length=6)
xdebug_debug_zval(b); //(refcount=1, is_ref=0),string 'aa' (length=6) 重新申请一个变量容器存储b,a的变量容器引用减1

引用的流程:

<?php
$a = 'aa';
xdebug_debug_zval('a'); //(refcount=1, is_ref=0),string 'aa' (length=2)
$b = & $a;
//变量容器的引用技术加1,引用标记置为1
xdebug_debug_zval('a'); //(refcount=2, is_ref=1),string 'aa' (length=2)
xdebug_debug_zval('b'); //(refcount=2, is_ref=1),string 'aa' (length=2)
$b = '123'; 
//php会发现,该容器变量是引用(is_ref),所以容器变量不用像赋值那样再申请一个
xdebug_debug_zval('a'); //(refcount=2, is_ref=1),string '123' (length=2)
xdebug_debug_zval('b'); //(refcount=2, is_ref=1),string '123' (length=2)
unset($b);
//变量容器应用计数减1,引用为零
xdebug_debug_zval('a'); //(refcount=1, is_ref=0),string '123' (length=2)
xdebug_debug_zval('b'); // b: no such symbol

那如果多次引用,unset掉一个,is_ref是否会被置为零,那样bug不就出现了么?变量容器还是引用啊。那么我们来看看:

<?php
$a = 'aa';
$b = &$a;
$c = &$a;
//可以看到引用refCount是3,is_ref永远是1
xdebug_debug_zval('a'); //(refcount=3, is_ref=1),string 'aa' (length=2)
xdebug_debug_zval('b'); //(refcount=3, is_ref=1),string 'aa' (length=2)
xdebug_debug_zval('c'); //(refcount=3, is_ref=1),string 'aa' (length=2)

unset($b);
//我们期待的bug没有出现,只是refcount减1,is_ref还是1
xdebug_debug_zval('a'); //(refcount=2, is_ref=1),string 'aa' (length=2)
xdebug_debug_zval('b'); // b: no such symbol
xdebug_debug_zval('c'); //(refcount=2, is_ref=1),string 'aa' (length=2)
//那php它怎么知道这个容器还有引用,毕竟is_ref仍然是1,不能计数,那么现在refcount就起作用了,是它告诉php,该变量有几个引用,但问题又来了,如果我干点坏事,在引用的时候,又赋值,它会不会有bug
$e = $a;
//我们看到期望的bug还是没出现,这时候再赋值,就不像直接赋值那么简单refcount加1了,而是申请了一个新的变量容器
xdebug_debug_zval('a'); //(refcount=2, is_ref=1),string 'aa' (length=2)
xdebug_debug_zval('e'); //(refcount=1, is_ref=0),string 'aa' (length=2)

unset和赋值null都能回收变量么?很多人都错认为,这两个都能回收变量空间,其实错了,null只是把变量占用的空间变小了,从回收上来说,该容器依然存在。

<?php
$a = 'aa';
$b = $a;
$b = null;
//又申请了一个变量容器
xdebug_debug_zval('a'); //(refcount=1, is_ref=0),string 'aa' (length=2)
xdebug_debug_zval('b'); //(refcount=1, is_ref=0),null 变量空间并没被回收
unset($b);
//这时候才释放了b变量容器的空间
xdebug_debug_zval('a'); //(refcount=1, is_ref=0),string 'aa' (length=2)
xdebug_debug_zval('b'); //b: no such symbol

总结

1. 垃圾回收的时机

PHP中,引用计数为0,则内存立刻释放。也就是说,不存在环状引用的变量,离开变量的作用域,内存被立刻释放。环状引用检测则是在满足一定条件下触发,所以在上面的例子中,会看到使用的内存有大幅度的波动。也可以通过 gc_collect_cycles 函数来主动进行环状引用检测。

2. &符号的影响

显式引用一个变量,会增加该内存的引用计数:

$a = "something";
$b = &$a;
此时unset($a), 但是仍有$b指向该内存区域的引用,内存不会释放。

3. unset函数的影响

unset只是断开一个变量到一块内存区域的连接,同时将该内存区域的引用计数-1;在上面的例子中,循环体内部,$a=new A(); unset($a);并不会将$a的引用计数减到零;

4. = null 操作的影响;

$a = null 是直接将$a 指向的数据结构置空,同时将其引用计数归0。

5. 脚本执行结束的影响

脚本执行结束,该脚本中使用的所有内存都会被释放,不论是否有引用环。

PHP 相关文章推荐
PHP5+UTF8多文件上传类
Oct 17 PHP
php 删除cookie和浏览器重定向
Mar 16 PHP
PHP爆绝对路径方法收集整理
Sep 17 PHP
php页面跳转代码 输入网址跳转到你定义的页面
Mar 28 PHP
PHP 获取远程文件大小的3种解决方法
Jul 11 PHP
Dwz与thinkphp整合下的数据导出到Excel实例
Dec 04 PHP
PHP结合jQuery实现找回密码
Jul 22 PHP
PHP查看当前变量类型的方法
Jul 31 PHP
PHP环形链表实现方法示例
Sep 15 PHP
TP5.0框架实现无限极回复功能的方法分析
May 04 PHP
php与阿里云短信接口接入操作案例分析
May 27 PHP
ThinkPHP5框架中使用JWT的方法示例
Jun 03 PHP
php实现xml与json之间的相互转换功能实例
Jul 07 #PHP
PHP登录验证码的实现与使用方法
Jul 07 #PHP
PHP常见的6个错误提示及解决方法
Jul 07 #PHP
php生成mysql的数据字典
Jul 07 #PHP
php自定义函数实现JS的escape的方法示例
Jul 07 #PHP
PHP使用mysql与mysqli连接Mysql数据库用法示例
Jul 07 #PHP
PHP使用自定义方法实现数组合并示例
Jul 07 #PHP
You might like
PHP CodeBase:将时间显示为&quot;刚刚&quot;&quot;n分钟/小时前&quot;的方法详解
2013/06/06 PHP
php格式输出文件var_export函数实例
2014/11/15 PHP
PHP递归遍历指定目录的文件并统计文件数量的方法
2015/03/24 PHP
PHP dirname(__FILE__)原理及用法解析
2020/10/28 PHP
Aster vs Newbee BO3 第三场2.18
2021/03/10 DOTA
JavaScript 获取事件对象的注意点
2009/07/29 Javascript
cnblogs中在闪存中屏蔽某人的实现代码
2010/11/14 Javascript
html+js实现动态显示本地时间
2013/09/21 Javascript
JS获取当前日期和时间的简单实例
2013/11/19 Javascript
javascript快速排序算法详解
2014/09/17 Javascript
jQuery中serializeArray()与serialize()的区别实例分析
2015/12/09 Javascript
Avalon中文长字符截取、关键字符隐藏、自定义过滤器
2016/05/18 Javascript
浅谈JQ中mouseover和mouseenter的区别
2016/09/13 Javascript
Bootstrap基本布局实现方法详解
2016/11/25 Javascript
浅析javascript中的Event事件
2016/12/09 Javascript
详解使用vue实现tab 切换操作
2017/07/03 Javascript
Angular模版驱动表单的使用总结
2018/05/05 Javascript
详解es6超好用的语法糖Decorator
2018/08/01 Javascript
JS打印彩色菱形的实例代码
2018/08/15 Javascript
在element-ui的select下拉框加上滚动加载
2019/04/18 Javascript
微信小程序实现的一键拨号功能示例
2019/04/24 Javascript
如何利用nodejs实现命令行游戏
2020/11/24 NodeJs
vue使用exif获取图片旋转,压缩的示例代码
2020/12/11 Vue.js
Python标准库之多进程(multiprocessing包)介绍
2014/11/25 Python
Python内置数据结构与操作符的练习题集锦
2016/07/01 Python
python实现多进程代码示例
2018/10/31 Python
Python面向对象程序设计类变量与成员变量、类方法与成员方法用法分析
2019/04/12 Python
python 并发编程 阻塞IO模型原理解析
2019/08/20 Python
Django REST Framework之频率限制的使用
2019/09/29 Python
Python无头爬虫下载文件的实现
2020/04/02 Python
法国时尚品牌乐都特瑞士站:La Redoute瑞士
2016/09/05 全球购物
十八届三中全会学习方案
2014/02/16 职场文书
2014年驾驶员工作总结
2014/11/18 职场文书
遗嘱格式范本
2015/08/07 职场文书
《为人民服务》教学反思
2016/02/20 职场文书
CSS几步实现赛博朋克2077风格视觉效果
2021/06/16 HTML / CSS