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 相关文章推荐
第十一节 重载 [11]
Oct 09 PHP
用PHP发电子邮件
Oct 09 PHP
ThinkPHP之getField详解
Jun 20 PHP
PHP使用GIFEncoder类处理gif图片实例
Jul 01 PHP
学习php中的正则表达式
Aug 17 PHP
PHP 双链表(SplDoublyLinkedList)简介和使用实例
May 12 PHP
PHP概率计算函数汇总
Sep 13 PHP
Symfony2实现在controller中获取url的方法
Mar 18 PHP
WordPress分页伪静态加html后缀
Jun 08 PHP
PHP使用mysqli操作MySQL数据库的简单方法
Feb 04 PHP
thinkphp3.2框架中where条件查询用法总结
Aug 13 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面向对象public private protected 访问修饰符
2013/06/30 PHP
php中header设置常见文件类型的content-type
2015/06/23 PHP
php layui实现前端多图上传实例
2019/07/30 PHP
在js(jquery)中获得文本框焦点和失去焦点的方法
2012/12/04 Javascript
jQuery实现复选框全选/取消全选/反选及获得选择的值
2014/06/12 Javascript
ajaxFileUpload.js插件支持多文件上传的方法
2014/09/02 Javascript
jQuery中on()方法用法实例详解
2015/02/06 Javascript
js使用DOM操作实现简单留言板的方法
2015/04/10 Javascript
javascript比较两个日期相差天数的方法
2015/07/24 Javascript
js实现搜索框关键字智能匹配代码
2020/03/26 Javascript
Bootstrap时间选择器datetimepicker和daterangepicker使用实例解析
2016/09/17 Javascript
JS刷新父窗口的几种方式小结(推荐)
2016/11/09 Javascript
微信小程序 支付简单实例及注意事项
2017/01/06 Javascript
微信小程序  checkbox组件详解及简单实例
2017/01/10 Javascript
Vue代码分割懒加载的实现方法
2017/11/23 Javascript
9种使用Chrome Firefox 自带调试工具调试javascript技巧
2017/12/22 Javascript
js删除数组中的元素delete和splice的区别详解
2018/02/03 Javascript
element-ui多文件上传的实现示例
2019/04/10 Javascript
用Vue.js方法创建模板并使用多个模板合成
2019/06/28 Javascript
解决vue axios跨域 Request Method: OPTIONS问题(预检请求)
2020/08/14 Javascript
react-intl实现React国际化多语言的方法
2020/09/27 Javascript
浅谈python中的数字类型与处理工具
2017/08/02 Python
python SSH模块登录,远程机执行shell命令实例解析
2018/01/12 Python
python中计算一个列表中连续相同的元素个数方法
2018/06/29 Python
python+numpy+matplotalib实现梯度下降法
2018/08/31 Python
用Python实现读写锁的示例代码
2018/11/05 Python
对python条件表达式的四种实现方法小结
2019/01/30 Python
在flask中使用python-dotenv+flask-cli自定义命令(推荐)
2020/01/05 Python
python3格式化字符串 f-string的高级用法(推荐)
2020/03/04 Python
用sleep间隔进行python反爬虫的实例讲解
2020/11/30 Python
简单叙述一下MYSQL的优化
2016/05/09 面试题
留学推荐信中文范文三篇
2014/01/25 职场文书
五四青年节演讲稿
2014/05/26 职场文书
正风肃纪剖析材料范文
2014/10/10 职场文书
辞职信的写法
2015/02/27 职场文书
Vue鼠标滚轮滚动切换路由效果的实现方法
2021/08/04 Vue.js