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 相关文章推荐
IIS下PHP连接数据库提示mysql undefined function mysql_connect()
Jun 04 PHP
提示Trying to clone an uncloneable object of class Imagic的解决
Oct 27 PHP
基于PHP一些十分严重的缺陷详解
Jun 03 PHP
PHP下的Oracle客户端扩展(OCI8)安装教程
Sep 10 PHP
了解PHP的返回引用和局部静态变量
Jun 04 PHP
PHP实现无限级分类(不使用递归)
Oct 22 PHP
WordPress中查询文章的循环Loop结构及用法分析
Dec 17 PHP
Linux环境下php实现给网站截图的方法
May 03 PHP
PHP list() 将数组中的值赋给变量的简单实例
Jun 13 PHP
PHP类型约束用法示例
Sep 28 PHP
PHP 二维关联数组根据其中一个字段排序(推荐)
Apr 04 PHP
PHP魔术方法之__call与__callStatic使用方法
Jul 23 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写的采集程序
2007/03/16 PHP
php学习笔记 php中面向对象三大特性之一[封装性]的应用
2011/06/13 PHP
php中$_GET与$_POST过滤sql注入的方法
2014/11/03 PHP
浅析Yii2 gridview实现批量删除教程
2016/04/22 PHP
wordpress网站转移到本地运行测试的方法
2017/03/15 PHP
自制PHP框架之模型与数据库
2017/05/07 PHP
页面中iframe相互传值传参
2009/12/13 Javascript
jquery的flexigrid无法显示数据提示获取到数据
2013/07/19 Javascript
jquery实现div拖拽宽度示例代码
2013/07/31 Javascript
使用jQuery实现星级评分代码分享
2014/12/09 Javascript
jquery验证邮箱格式并显示提交按钮
2015/11/07 Javascript
html+js实现简单的计算器代码(加减乘除)
2016/07/12 Javascript
值得分享的Bootstrap Table使用教程
2016/11/23 Javascript
响应式框架Bootstrap栅格系统的实例
2017/12/19 Javascript
jQuery获取所有父级元素及同级元素及子元素的方法(推荐)
2018/01/21 jQuery
vue自定义指令实现方法详解
2019/02/11 Javascript
微信小程序结合mock.js实现后台模拟及调试
2019/03/28 Javascript
记录vue做微信自定义分享的一些问题
2019/09/12 Javascript
vue 导航菜单刷新状态不消失,显示对应的路由界面操作
2020/08/06 Javascript
让Python代码更快运行的5种方法
2015/06/21 Python
Python利用Beautiful Soup模块搜索内容详解
2017/03/29 Python
python3 selenium 切换窗口的几种方法小结
2018/05/21 Python
Python设计模式之组合模式原理与用法实例分析
2019/01/11 Python
python的pygal模块绘制反正切函数图像方法
2019/07/16 Python
初次部署django+gunicorn+nginx的方法步骤
2019/09/11 Python
如何在mac环境中用python处理protobuf
2019/12/25 Python
Python如何使用input函数获取输入
2020/08/06 Python
python 爬取英雄联盟皮肤并下载的示例
2020/12/04 Python
美国祛痘、抗衰老药妆品牌:Murad
2016/08/27 全球购物
经济学人订阅:The Economist
2018/07/19 全球购物
机械工程师求职自我评价
2013/09/23 职场文书
教师批评与自我批评总结
2014/10/16 职场文书
复试通知单模板
2015/04/24 职场文书
如何用JavaScript学习算法复杂度
2021/04/30 Javascript
MySQL系列之十 MySQL事务隔离实现并发控制
2021/07/02 MySQL
Spring Cloud Gateway去掉url前缀
2021/07/15 Java/Android