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 301转向实现代码
Sep 18 PHP
Ha0k 0.3 PHP 网页木马修改版
Oct 11 PHP
Yii框架中 find findAll 查找出制定的字段的方法对比
Sep 10 PHP
PHP链接MySQL的常用扩展函数
Oct 23 PHP
PHP判断一个gif图片是否为动态图片的方法
Nov 19 PHP
php将数组存储为文本文件方法汇总
Oct 28 PHP
PHP基于单例模式实现的数据库操作基类
Jan 15 PHP
PHP简单读取PDF页数的实现方法
Jul 21 PHP
PHP 输出缓冲控制(Output Control)详解
Aug 25 PHP
PHP+Redis开发的书签案例实战详解
Jul 09 PHP
PHP下载文件函数与用法示例
Sep 27 PHP
WordPress伪静态规则设置代码实例
Dec 10 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
WinXP + Apache +PHP5 + MySQL + phpMyAdmin安装全功略
2006/07/09 PHP
为查询结果建立向后/向前按钮
2006/10/09 PHP
php 判断字符串编码是utf-8 或gb2312实例
2016/11/01 PHP
解决Laravel5.2 Auth认证退出失效的问题
2019/10/14 PHP
php 中self,this的区别和操作方法实例分析
2019/11/04 PHP
jquery创建div 实现代码
2009/04/27 Javascript
jQuery-ui中自动完成实现方法
2010/06/10 Javascript
关于JavaScript与HTML的交互事件
2013/04/12 Javascript
将文本输入框内容加入表中的js代码
2013/08/18 Javascript
Jquery对象和Dom对象的区别分析
2014/11/20 Javascript
location.hash保存页面状态的技巧
2016/04/28 Javascript
利用jquery实现验证输入的是否是数字、小数,包含保留几位小数
2016/12/07 Javascript
常用jQuery选择器汇总
2017/02/02 Javascript
React Native第三方平台分享的实例(Android,IOS双平台)
2017/08/04 Javascript
vue mounted 调用两次的完美解决办法
2018/10/29 Javascript
配置一个vue3.0项目的完整步骤
2019/04/26 Javascript
vue实现滑动到底部加载更多效果
2020/10/27 Javascript
[49:35]LGD vs OG 2018国际邀请赛淘汰赛BO3 第二场 8.25
2018/08/29 DOTA
[01:16:37]【全国守擂赛】第三周决赛 Dark Knight vs. 一个弱队
2020/05/04 DOTA
Python抓取框架 Scrapy的架构
2016/08/12 Python
详解python中xlrd包的安装与处理Excel表格
2016/12/16 Python
python字符串中的单双引
2017/02/16 Python
python pygame模块编写飞机大战
2018/11/20 Python
python 实现矩阵上下/左右翻转,转置的示例
2019/01/23 Python
pyqt5利用pyqtDesigner实现登录界面
2019/03/28 Python
使用Fabric自动化部署Django项目的实现
2019/09/27 Python
python 瀑布线指标编写实例
2020/06/03 Python
芬兰攀岩、山地运动和户外活动用品购物网站:Bergfreunde
2016/10/06 全球购物
Champs Sports加拿大:北美最大的以商场为基础的专业运动鞋和服装零售商之一
2018/05/01 全球购物
main 主函数执行完毕后,是否可能会再执行一段代码,给出说明
2012/12/05 面试题
电子专业推荐信范文
2013/11/18 职场文书
回门宴父母答谢词
2014/01/26 职场文书
2014新课程改革心得体会
2014/03/10 职场文书
党员创先争优公开承诺书
2014/03/28 职场文书
班干部学习委员竞选稿
2015/11/20 职场文书
学会Python数据可视化必须尝试这7个库
2021/06/16 Python