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桌面中心(二) 数据库写入
Mar 11 PHP
PHP5.2下chunk_split()函数整数溢出漏洞 分析
Jun 06 PHP
ionCube 一款类似zend的PHP加密/解密工具
Jul 25 PHP
php读取mssql的ntext字段返回值为空的解决方法
Dec 30 PHP
Laravel中使用自己编写类库的3种方法
Feb 10 PHP
Smarty中的注释和截断功能介绍
Apr 09 PHP
判断、添加和删除WordPress置顶文章的相关PHP函数小结
Dec 10 PHP
利用php_imagick实现复古效果的方法
Oct 18 PHP
PHP 搜索查询功能实现
Nov 29 PHP
php生成网页桌面快捷方式
May 05 PHP
TP5(thinkPHP框架)实现后台清除缓存功能示例
May 29 PHP
laravel config文件配置全局变量的例子
Oct 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 serialize()与unserialize()的用法
2013/06/05 PHP
thinkphp实现like模糊查询实例
2014/10/29 PHP
php获取文件名称和扩展名的方法
2017/02/07 PHP
Yii2中多表关联查询hasOne hasMany的方法
2017/02/15 PHP
PHP封装的PDO数据库操作类实例
2017/06/21 PHP
javascript实现的动态文字变换
2007/07/28 Javascript
基于jQuery的仿flash的广告轮播代码
2010/11/04 Javascript
jQuery当鼠标悬停时放大图片的效果实例
2013/07/03 Javascript
js截取字符串的两种方法及区别详解
2013/11/05 Javascript
jQuery的事件委托实例分析
2015/07/15 Javascript
javascript每日必学之循环
2016/02/19 Javascript
json传值以及ajax接收详解
2016/05/24 Javascript
jQuery实现的导航下拉菜单效果
2016/07/04 Javascript
JS得到当前时间的方法示例
2017/03/24 Javascript
vue-cli脚手架引入弹出层layer插件的几种方法
2019/06/24 Javascript
浅谈vue中$event理解和框架中在包含默认值外传参
2020/08/07 Javascript
基于vue与element实现创建试卷相关功能(实例代码)
2020/12/07 Vue.js
python的debug实用工具 pdb详解
2019/07/12 Python
python配置文件写入过程详解
2019/10/19 Python
numpy:np.newaxis 实现将行向量转换成列向量
2019/11/30 Python
Python绘图之二维图与三维图详解
2020/08/04 Python
基于python获取本地时间并转换时间戳和日期格式
2020/10/27 Python
详解canvas绘制多张图的排列顺序问题
2019/01/21 HTML / CSS
加拿大鞋子连锁店:Town Shoes
2016/09/26 全球购物
美国电子元器件分销商:Newark element14
2018/01/13 全球购物
英国第一的滑雪服装和装备零售商:Snow+Rock
2020/02/01 全球购物
Java程序开发中如何应用线程
2016/03/03 面试题
JAVA程序设计笔试题面试题一套
2015/07/28 面试题
涉外经济法专业毕业生推荐信
2013/11/24 职场文书
森林防火标语
2014/06/23 职场文书
2014年乡镇领导个人整改措施
2014/09/19 职场文书
教师四风问题对照检查材料
2014/09/26 职场文书
基层党组织整改方案
2014/10/25 职场文书
民事答辩状格式范文
2015/05/21 职场文书
CSS font-variation 可变字体的魅力(实例详解)
2022/03/03 HTML / CSS
JavaWeb Servlet开发注册页面实例
2022/04/11 Java/Android