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中在数据库中保存Checkbox数据(2)
Oct 09 PHP
PHP的中问验证码
Nov 25 PHP
用mysql触发器自动更新memcache的实现代码
Oct 11 PHP
让php处理图片变得简单 基于gb库的图片处理类附实例代码下载
May 17 PHP
apache php模块整合操作指南
Nov 16 PHP
php多文件上传下载示例分享
Feb 20 PHP
PHP使用range协议实现输出文件断点续传代码实例
Jul 04 PHP
PHP实现动态web服务器方法
Jul 29 PHP
Nginx环境下PHP flush失效的解决方法
Oct 19 PHP
PHP多进程编程之僵尸进程问题的理解
Oct 15 PHP
Laravel 模型关联基础教程详解
Sep 17 PHP
基于laravel Request的所有方法详解
Sep 29 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 object转数组示例
2014/01/15 PHP
PHP 双链表(SplDoublyLinkedList)简介和使用实例
2015/05/12 PHP
javascript下查找父节点的简单方法
2007/08/13 Javascript
javascript eval和JSON之间的联系
2009/12/31 Javascript
jquery 关于event.target使用的几点说明介绍
2013/04/26 Javascript
jQuery根据纬度经度查看地图处理程序
2013/05/08 Javascript
Jquery 模板数据绑定插件的使用方法详解
2013/07/08 Javascript
JS实现根据当前文字选择返回被选中的文字
2014/05/21 Javascript
angularJS与bootstrap结合实现动态加载弹出提示内容
2015/10/16 Javascript
babel基本使用详解
2017/02/17 Javascript
详解nodeJS之二进制buffer对象
2017/06/03 NodeJs
vue中$nextTick的用法讲解
2019/01/17 Javascript
vue中filters 传入两个参数 / 使用两个filters的实现方法
2019/07/15 Javascript
Vue简单实现原理详解
2020/05/07 Javascript
jQuery+ThinkPHP实现图片上传
2020/07/23 jQuery
从零学python系列之浅谈pickle模块封装和拆封数据对象的方法
2014/05/23 Python
Python while 循环使用的简单实例
2016/06/08 Python
python使用标准库根据进程名如何获取进程的pid详解
2017/10/31 Python
Python使用django框架实现多人在线匿名聊天的小程序
2017/11/29 Python
利用Python如何批量更新服务器文件
2018/07/29 Python
Python中如何将一个类方法变为多个方法
2019/12/30 Python
python图形开发GUI库pyqt5的基本使用方法详解
2020/02/14 Python
python3注册全局热键的实现
2020/03/22 Python
Python中用xlwt制作表格实例讲解
2020/11/05 Python
python安装sklearn模块的方法详解
2020/11/28 Python
html5+css如何实现中间大两头小的轮播效果
2018/12/06 HTML / CSS
什么时候用assert
2015/05/08 面试题
Exception类的常用方法
2012/06/16 面试题
市场营销专业个人自荐信格式
2013/09/21 职场文书
安全负责人任命书
2014/06/06 职场文书
重阳节慰问信
2015/02/15 职场文书
家庭教育培训学习心得体会
2016/01/14 职场文书
基于Go语言构建RESTful API服务
2021/07/25 Golang
简述Java中throw-throws异常抛出
2021/08/07 Java/Android
vue实现移动端div拖动效果
2022/03/03 Vue.js
本地搭建minio文件服务器(使用bat脚本启动)的方法
2022/07/15 Servers