PHP对象相互引用的内存溢出实例分析


Posted in PHP onAugust 28, 2014

通常来说使用脚本语言最大的好处之一就是可利用其拥有的自动垃圾回收机制来释放内存。你不需要在使用完变量后做任何释放内存的处理,因为这些PHP会帮你完成。
当然,我们可以按自己的意愿调用 unset() 函数来释放内存,但通常不需要这么做。
不过在PHP里,至少有一种情况内存不会得到自动释放,即便是手动调用 unset()。详情可考PHP官网关于内存泄露的分析:http://bugs.php.net/bug.php?id=33595。

问题症状如下:

如果两个对象之间存在着相互引用的关系,如“父对象-子对象”,对父对象调用 unset()不会释放在子对象中引用父对象的内存(即便父对象被垃圾回收,也不行)。

是不是有些糊涂了?我们来看下面的这段代码:

<?
phpclass Foo {
 function __construct(){
 $this->bar = new Bar($this);
 }
}
class Bar {
 function __construct($foo = null){
 $this->foo = $foo;
 }
}
while (true) {
 $foo = new Foo();
 unset($foo);
 echo number_format(memory_get_usage()) . " ";
}
?>

运行这段代码,你会看到内存使用率越来越高越来越高,直到用光光。

...33,551,61633,551,97633,552,33633,552,696PHP Fatal error: Allowed memory size of 33554432 bytes exhausted(tried to allocate 16 bytes) in memleak.php on line 17

对大部分PHP程序员来讲这种情况不算是什么问题。可如果你在一个长期运行的代码中使用到了一大堆相互引用的对象,尤其是在对象相对较大的情况下,内存会迅速地消耗殆尽。

Userland解决方案

虽然有些乏味、不优雅,但之前提到的 bugs.php.net 链接中提供了一个解决方案。
这个方案在释放对象前使用一个 destructor 方法以达到目的。Destructor 方法可将所有内部的父对象引用全部清除,也就是说可以将这部分本来会溢出的内存释放掉。

以下是“修复后”的代码:

<?
phpclass Foo {
 function __construct(){
 $this->bar = new Bar($this);
 }
 function __destruct(){
 unset($this->bar);
 }
}
class Bar {
 function __construct($foo = null){
 $this->foo = $foo;
 }
}
while (true) {
 $foo = new Foo();
 $foo->__destruct();
 unset($foo);
 echo number_format(memory_get_usage()) . " ";
}
?>

注意那个新增的Foo::__destruct()方法,以及在释放对象前对 $foo->__destruct() 的调用。现在这段代码解决了内存使用率一直增加的问题,这么一来,代码就可以很好的工作了。

PHP内核解决方案

为什么会有内存溢出的发生?我对PHP内核方面的研究并不精通,但可以确定的是此问题与引用计数有关系。
在 $bar 中引用 $foo 的引用计数不会因为父对象 $foo 被释放而递减,这时PHP认为你仍需要 $foo 对象,也就不会释放这部分的内存。原理大致如此。

通俗的来说,大体意思是:一个引用计数没有递减,所以一些内存永远得不到释放。
此外在前面提到的 bugs.php.net 链接中指出了修改垃圾回收的过程将会牺牲极大的性能,需要读者对此注意。

与其改变垃圾回收的过程,为什么不用 unset() 对内部对象做释放的工作呢?(或者在释放对象的时候调用 __destruct()?)
也许PHP内核开发者可以在此或其他地方,对这种垃圾回收处理机制做出修改。

相信本文所述对大家深入理解PHP运行原理有所帮助。

PHP 相关文章推荐
PHP4实际应用经验篇(6)
Oct 09 PHP
PHP+ACCESS 文章管理程序代码
Jun 21 PHP
php操作SVN版本服务器类代码
Nov 27 PHP
ucenter通信原理分析
Jan 09 PHP
PHP中imagick函数的中文解释
Jan 21 PHP
Laravel 5框架学习之环境与配置
Apr 08 PHP
php可扩展的验证类实例(可对邮件、手机号、URL等验证)
Jul 09 PHP
ThinkPHP 整合Bootstrap Ajax分页样式
Dec 23 PHP
完美解决在ThinkPHP控制器中命名空间的问题
May 05 PHP
PHP使用PDO创建MySQL数据库、表及插入多条数据操作示例
May 30 PHP
php设计模式之工厂模式用法经典实例分析
Sep 20 PHP
让你的PHP,APACHE,NGINX支持大文件上传
Mar 09 PHP
PHP对象递归引用造成内存泄漏分析
Aug 28 #PHP
PHP中cookie和session的区别实例分析
Aug 28 #PHP
PHP实现视频文件上传完整实例
Aug 28 #PHP
PHP获取表单所有复选框的值的方法
Aug 28 #PHP
PHP中echo和print的区别
Aug 28 #PHP
什么情况下可以不写PHP的闭合标签“?&gt;”
Aug 28 #PHP
PHP防盗链代码实例
Aug 27 #PHP
You might like
PHP实现的功能是显示8条基色色带
2006/10/09 PHP
杏林同学录(一)
2006/10/09 PHP
PHP DataGrid 实现代码
2009/08/12 PHP
PHP获取文件后缀名的三个函数
2012/10/15 PHP
PHP入门教程之上传文件实例详解
2016/09/11 PHP
使用SMB共享来绕过php远程文件包含的限制执行RFI的利用
2019/05/31 PHP
数组Array进行原型prototype扩展后带来的for in遍历问题
2010/02/07 Javascript
Jquery实现三层遍历删除功能代码
2013/04/23 Javascript
Jquery创建一个层当鼠标移动到层上面不消失效果
2013/12/12 Javascript
js统计页面的来访次数实现代码
2014/05/09 Javascript
javascript搜索框点击文字消失失焦时文本出现
2014/09/18 Javascript
js+css实现超简洁的二级下拉菜单效果代码
2015/09/07 Javascript
AngularJs directive详解及示例代码
2016/09/01 Javascript
JS匿名函数类生成方式实例分析
2016/11/26 Javascript
JS获得一个对象的所有属性和方法实例
2017/02/21 Javascript
JS实现微信里判断页面是否被分享成功的方法
2017/06/06 Javascript
快速搭建React的环境步骤详解
2017/11/06 Javascript
Vue 项目部署到服务器的问题解决方法
2017/12/05 Javascript
js动态获取时间的方法分析
2019/08/02 Javascript
python简单实现基于SSL的IRC bot实例
2015/06/15 Python
简单实现python聊天程序
2018/04/01 Python
python random从集合中随机选择元素的方法
2019/01/23 Python
python os.path.isfile()因参数问题判断错误的解决
2019/11/29 Python
python 类之间的参数传递方式
2019/12/20 Python
python GUI库图形界面开发之PyQt5窗口控件QWidget详细使用方法
2020/02/26 Python
python之生成多层json结构的实现
2020/02/27 Python
Python configparser模块配置文件过程解析
2020/03/03 Python
Python爬虫之Selenium鼠标事件的实现
2020/12/04 Python
毕业生简单求职信
2013/11/19 职场文书
见习期自我鉴定范文
2014/03/19 职场文书
2014年公司工作总结
2014/11/22 职场文书
工艺技术员岗位职责
2015/02/04 职场文书
2015年大班保育员工作总结
2015/05/18 职场文书
合同补充协议书
2016/03/24 职场文书
教你如何使用Python开发一个钉钉群应答机器人
2021/06/21 Python
Python万能模板案例之matplotlib绘制直方图的基本配置
2022/04/13 Python