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 相关文章推荐
php设计模式  Command(命令模式)
Jun 17 PHP
PHP类与对象中的private访问控制的疑问
Nov 01 PHP
php传值赋值和传地址赋值用法实例分析
Jun 20 PHP
利用PHP脚本在Linux下用md5函数加密字符串的方法
Jun 29 PHP
在Debian系统下配置LNMP的教程
Jul 09 PHP
PHP表单数据写入MySQL数据库的代码
May 31 PHP
微信支付的开发流程详解
Sep 13 PHP
PHP简单数据库操作类实例【支持增删改查及链式操作】
Oct 10 PHP
PHP编程实现计算抽奖概率算法完整实例
Aug 09 PHP
PHP实现二维数组中的查找算法小结
Jun 09 PHP
php版本CKEditor 4和CKFinder安装及配置方法图文教程
Jun 05 PHP
php多进程并发编程防止出现僵尸进程的方法分析
Feb 28 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命名空间(Namespace)的使用详解
2013/05/04 PHP
php include类文件超时问题处理
2015/02/06 PHP
PHP嵌套输出缓冲代码实例
2015/05/12 PHP
php实现的错误处理封装类实例
2017/06/20 PHP
JavaScript获取图片真实大小代码实例
2014/09/24 Javascript
jquery实现可拖拽弹出层特效
2015/01/04 Javascript
JavaScript数据库TaffyDB用法实例分析
2015/07/27 Javascript
js实现精确到秒的日期选择器完整实例
2016/04/30 Javascript
javascript用正则表达式过滤空格的实现代码
2016/06/14 Javascript
AngularJS ng-bind 指令简单实现
2016/07/30 Javascript
js判断一个字符串是以某个字符串开头的简单实例
2016/12/27 Javascript
javascript实现简单的可随机变色网页计算器示例
2016/12/30 Javascript
JS基于递归算法实现1,2,3,4,5,6,7,8,9倒序放入数组中的方法
2017/01/03 Javascript
Restify中接入Socket.io报Error:Can’t set headers的错误解决
2017/03/28 Javascript
解决Vue 通过下表修改数组,页面不渲染的问题
2018/03/08 Javascript
layui自己添加图片按钮并点击跳转页面的例子
2019/09/14 Javascript
Vue路由切换页面不更新问题解决方案
2020/07/10 Javascript
python实现马耳可夫链算法实例分析
2015/05/20 Python
Tornado协程在python2.7如何返回值(实现方法)
2017/06/22 Python
200 行python 代码实现 2048 游戏
2018/01/12 Python
python解析json串与正则匹配对比方法
2018/12/20 Python
python实现维吉尼亚加密法
2019/03/20 Python
Python配置虚拟环境图文步骤
2019/05/20 Python
Python绘制堆叠柱状图的实例
2019/07/09 Python
Django 限制访问频率的思路详解
2019/12/24 Python
天美时手表加拿大官网:Timex加拿大
2016/09/01 全球购物
德国网上花店:Valentins
2018/08/15 全球购物
英国健康和美容技术产品购物网站:CurrentBody
2019/07/17 全球购物
运动会入场词50字
2014/02/20 职场文书
说明书怎么写
2014/05/06 职场文书
冬季安全检查方案
2014/05/23 职场文书
个人房屋转让协议书范本
2014/10/26 职场文书
2015年春节标语口号
2014/12/09 职场文书
2015年艾滋病宣传活动总结
2015/03/27 职场文书
对Keras自带Loss Function的深入研究
2021/05/25 Python
详解MySQL的内连接和外连接
2023/05/08 MySQL