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 相关文章推荐
使用sockets:从新闻组中获取文章(二)
Oct 09 PHP
phpfans留言版用到的install.php
Jan 04 PHP
php ajax 静态分页过程形式
Sep 02 PHP
php通过strpos查找字符串出现位置的方法
Mar 17 PHP
ThinkPHP开发框架函数详解:C方法
Aug 14 PHP
Zend Framework教程之Autoloading用法详解
Mar 08 PHP
php实现博客,论坛图片防盗链的方法
Oct 15 PHP
PHP实现微信模拟登陆并给用户发送消息的方法【文字,图片,图文】
Jun 29 PHP
php+redis消息队列实现抢购功能
Feb 08 PHP
PHP赋值的内部是如何跑的详解
Jan 13 PHP
TP5框架实现一次选择多张图片并预览的方法示例
Apr 04 PHP
PHP设计模式(五)适配器模式Adapter实例详解【结构型】
May 02 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
收听短波不可能有声音清晰的品质吗
2021/03/01 无线电
PHP5与MySQL数据库操作常用代码 收集
2010/03/21 PHP
php使用parse_str实现查询字符串解析到变量中的方法
2017/02/17 PHP
ExtJS 设置级联菜单的默认值
2010/06/13 Javascript
javascript的回调函数应用示例
2014/02/20 Javascript
php利用curl获取远程图片实现方法
2015/10/26 Javascript
深入理解jquery自定义动画animate()
2016/05/24 Javascript
jquery 点击元素后,滚动条滚动至该元素位置的方法
2016/08/05 Javascript
Yarn的安装与使用详细介绍
2016/10/25 Javascript
Node.js使用Express创建Web项目详细教程
2017/03/31 Javascript
深入探究AngularJs之$scope对象(作用域)
2017/07/20 Javascript
js Element Traversal规范中的元素遍历方法
2018/04/19 Javascript
Vue Extends 扩展选项用法完整实例
2019/09/17 Javascript
js前端对于大量数据的展示方式及处理方法
2020/12/02 Javascript
[04:13]2018国际邀请赛典藏宝瓶Ⅱ饰品一览
2018/07/21 DOTA
Python实现的数据结构与算法之基本搜索详解
2015/04/22 Python
VScode编写第一个Python程序HelloWorld步骤
2018/04/06 Python
解决pip install的时候报错timed out的问题
2018/06/12 Python
python读取mysql数据绘制条形图
2020/03/25 Python
使用jupyter Nodebook查看函数或方法的参数以及使用情况
2020/04/14 Python
Python在字符串中处理html和xml的方法
2020/07/31 Python
英国最红的高街时尚品牌:Topshop
2016/08/05 全球购物
美国知名的摄影器材销售网站:Adorama
2017/02/01 全球购物
怎么可以提高数据库查询数据的速度
2014/06/28 面试题
2013年保送生自荐信格式
2013/11/20 职场文书
业务员薪酬管理制度
2014/01/15 职场文书
驾驶员岗位职责
2014/01/29 职场文书
个人社会实践自我鉴定
2014/03/24 职场文书
社会治安综合治理管理责任书
2014/04/16 职场文书
经济类毕业生求职信
2014/06/26 职场文书
土地租赁意向书
2014/07/30 职场文书
小学生勤俭节约演讲稿
2014/08/28 职场文书
2015年优质护理服务工作总结
2015/04/08 职场文书
《春酒》教学反思
2016/02/22 职场文书
学习计划是什么
2019/04/30 职场文书
python数字图像处理之对比度与亮度调整示例
2022/06/28 Python