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 相关文章推荐
PHP Header用于页面跳转要注意的几个问题总结
Oct 03 PHP
php设计模式 Builder(建造者模式)
Jun 26 PHP
解析php5配置使用pdo
Jul 03 PHP
PHP、Nginx、Apache中禁止网页被iframe引用的方法
Oct 01 PHP
php+memcache实现的网站在线人数统计代码
Jul 04 PHP
ThinkPHP路由详解
Jul 27 PHP
Zend Framework教程之Application和Bootstrap用法详解
Mar 10 PHP
PHP数字金额转换成中文大写显示
Jan 05 PHP
PHP文件后缀不强制为.php方法
Mar 31 PHP
php引用和拷贝的区别知识点总结
Sep 23 PHP
php模式设计之观察者模式应用实例分析
Sep 25 PHP
PHP vsprintf()函数格式化字符串操作原理解析
Jul 14 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
生成缩略图
2006/10/09 PHP
用PHP连mysql和oracle数据库性能比较
2006/10/09 PHP
php下实现折线图效果的代码
2007/04/28 PHP
PHP  Yii清理缓存的实现方法
2016/11/10 PHP
PHP学习笔记之session
2018/05/06 PHP
在JavaScript中监听IME键盘输入事件
2011/05/29 Javascript
ExtJS 刷新后如何默认选中刷新前最后一次选中的节点
2014/04/03 Javascript
更快的异步执行(setTimeout多浏览器)
2014/08/12 Javascript
使用javascript实现监控视频播放并打印日志
2015/01/05 Javascript
js漂浮广告实现代码
2015/08/15 Javascript
JavaScript的removeChild()函数用法详解
2015/12/27 Javascript
详解JS中Array对象扩展与String对象扩展
2016/01/07 Javascript
轻松实现javascript图片轮播特效
2016/01/13 Javascript
js 获取范围内的随机数实例代码
2016/08/02 Javascript
NodeJS实现图片上传代码(Express)
2017/06/30 NodeJs
vue.js数据绑定操作详解
2018/04/23 Javascript
js实现自定义右键菜单
2020/05/18 Javascript
javascript前端实现多视频上传
2020/12/13 Javascript
python基础入门详解(文件输入/输出 内建类型 字典操作使用方法)
2013/12/08 Python
Python使用urllib2模块抓取HTML页面资源的实例分享
2016/05/03 Python
详解Python 2.6 升级至 Python 2.7 的实践心得
2017/04/27 Python
python实现写数字文件名的递增保存文件方法
2018/10/25 Python
Django Channels 实现点对点实时聊天和消息推送功能
2019/07/17 Python
OpenCV里的imshow()和Matplotlib.pyplot的imshow()的实现
2019/11/25 Python
mac在matplotlib中显示中文的操作方法
2020/03/06 Python
Python基于QQ邮箱实现SSL发送
2020/04/26 Python
清除canvas画布内容(点擦除+线擦除)
2020/08/12 HTML / CSS
蒙蒂塞罗商店:Monticello Shop
2018/11/25 全球购物
中学教师管理制度
2014/01/14 职场文书
面试后的英文感谢信
2014/02/01 职场文书
2014年百日安全生产活动总结
2014/05/04 职场文书
新颖的化妆品活动方案
2014/08/21 职场文书
工人先锋号申报材料
2014/12/29 职场文书
HTML+CSS+JS实现图片的瀑布流布局的示例代码
2021/04/22 HTML / CSS
PostgreSQL13基于流复制搭建后备服务器的方法
2022/01/18 PostgreSQL
Win11如何启用启动修复 ? Win11执行启动修复的三种方法
2022/04/08 数码科技