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 相关文章推荐
图书管理程序(一)
Oct 09 PHP
PHP has encountered an Access Violation
Jan 15 PHP
解析将多维数组转换为支持curl提交的一维数组格式
Jul 08 PHP
对淘宝URL中ID提取的PHP代码
Sep 01 PHP
zf框架db类的分页示例分享
Mar 14 PHP
PHP中FTP相关函数小结
Jul 15 PHP
使用php完成常见的文件上传功能(推荐)
Jan 13 PHP
tp5实现微信小程序多图片上传到服务器功能
Jul 16 PHP
php代码调试利器firephp安装与使用方法分析
Aug 21 PHP
PHP attributes()函数讲解
Feb 03 PHP
PHP实现带进度条的Ajax文件上传功能示例
Jul 02 PHP
关于laravel 子查询 &amp; join的使用
Oct 16 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使用PDO方法详解
2014/12/27 PHP
PHP面向对象学习之parent::关键字
2017/01/18 PHP
基于jQuery的弹出消息插件 DivAlert之旅(一)
2010/04/01 Javascript
ExtJs事件机制基本代码模型和流程解析
2010/10/24 Javascript
基于Unit PNG Fix.js有时候在ie6下不正常的解决办法
2013/06/26 Javascript
js 判断计算字符串长度/判断空的简单方法
2013/08/05 Javascript
javascript实例--教你实现扑克牌洗牌功能
2014/05/15 Javascript
JS常用字符串处理方法应用总结
2014/05/22 Javascript
判断浏览器的内核及版本号方法汇总
2015/01/05 Javascript
有效提高JavaScript执行效率的几点知识
2015/01/31 Javascript
JS实现显示带倒影的图片横排居中放大展示特效实例【测试可用】
2016/08/23 Javascript
jQuery插件zTree实现删除树节点的方法示例
2017/03/08 Javascript
angularJS利用ng-repeat遍历二维数组的实例代码
2017/06/03 Javascript
微信小程序 http请求的session管理
2017/06/07 Javascript
vue获取当前点击的元素并传值的实例
2018/03/09 Javascript
js实现查询商品案例
2020/07/22 Javascript
Python脚本实现虾米网签到功能
2016/04/12 Python
详解Python的collections模块中的deque双端队列结构
2016/07/07 Python
Python类的继承、多态及获取对象信息操作详解
2019/02/28 Python
PyTorch搭建多项式回归模型(三)
2019/05/22 Python
Python logging模块进行封装实现原理解析
2020/08/07 Python
如何在python中处理配置文件代码实例
2020/09/27 Python
HTML5 canvas画图并保存成图片的jcanvas插件
2014/01/17 HTML / CSS
Merrell迈乐澳大利亚网站:购买户外登山鞋
2017/05/28 全球购物
新西兰Bookabach:查找全球度假屋
2020/12/03 全球购物
Android面试宝典
2013/08/06 面试题
What's the difference between an interface and abstract class? (接口与抽象类有什么区别)
2012/10/29 面试题
兼职学生的自我评价
2013/11/24 职场文书
大学生怎样进行自我评价
2013/12/07 职场文书
迟到检讨书900字
2014/01/14 职场文书
同学聚会老师邀请函
2014/01/28 职场文书
员工保密协议书
2014/09/27 职场文书
员工辞职信范文
2015/03/02 职场文书
TensorFlow中tf.batch_matmul()的用法
2021/06/02 Python
SQL实现LeetCode(196.删除重复邮箱)
2021/08/07 MySQL
使用python绘制横竖条形图
2022/04/21 Python