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 相关文章推荐
人大复印资料处理程序_输入篇
Oct 09 PHP
php中$this-&amp;gt;含义分析
Nov 29 PHP
PHP explode()函数用法、切分字符串
Oct 03 PHP
PHP数据集构建JSON格式及新数组的方法
Nov 07 PHP
PHP中VC6、VC9、TS、NTS版本的区别与用法详解
Oct 26 PHP
Linux中为php配置伪静态
Dec 17 PHP
PHP实现加强版加密解密类实例
Jul 29 PHP
php通过curl添加cookie伪造登陆抓取数据的方法
Apr 02 PHP
php的4种常用运行方式详解
Dec 22 PHP
PHP中仿制 ecshop验证码实例
Jan 06 PHP
PHP实现登陆表单提交CSRF及验证码
Jan 24 PHP
PHP获取二叉树镜像的方法
Jan 17 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 正则学习实例
2008/07/30 PHP
神盾加密解密教程(一)PHP变量可用字符
2014/05/28 PHP
RSA实现JS前端加密与PHP后端解密功能示例
2019/08/05 PHP
JS 获取span标签中的值的代码 支持ie与firefox
2009/08/24 Javascript
javascript 简练的几个函数
2009/08/29 Javascript
利用jquery包将字符串生成二维码图片
2013/09/12 Javascript
JSONP跨域的原理解析及其实现介绍
2014/03/22 Javascript
js 去除字符串第一位逗号的方法
2014/06/07 Javascript
node.js中的fs.readlink方法使用说明
2014/12/17 Javascript
JS实现的最简Table选项卡效果
2015/10/14 Javascript
Bootstrap中点击按钮后变灰并显示加载中实例代码
2016/09/23 Javascript
设置jquery UI 控件的大小方法
2016/12/12 Javascript
bootstrap为水平排列的表单和内联表单设置可选的图标
2017/02/15 Javascript
微信小程序之数据双向绑定与数据操作
2017/05/12 Javascript
利用pm2部署多个node.js项目的配置教程
2017/10/22 Javascript
MUI 实现侧滑菜单及其主体部分上下滑动的方法
2018/01/25 Javascript
使用weixin-java-tools完成微信授权登录、微信支付的示例
2018/09/26 Javascript
解决vue-loader加载不上的问题
2020/10/21 Javascript
Vue router安装及使用方法解析
2020/12/02 Vue.js
python 实现堆排序算法代码
2012/06/05 Python
基于python实现百度翻译功能
2019/05/09 Python
Python基础学习之基本数据结构详解【数字、字符串、列表、元组、集合、字典】
2019/06/18 Python
python aiohttp的使用详解
2019/06/20 Python
python调用Matplotlib绘制分布点图
2019/10/18 Python
如何使用Python脚本实现文件拷贝
2019/11/20 Python
python统计函数库scipy.stats的用法解析
2020/02/25 Python
TensorFlow keras卷积神经网络 添加L2正则化方式
2020/05/22 Python
英国最好的温室之家:Greenhouses Direct
2019/07/13 全球购物
哈萨克斯坦最大的时装、鞋子和配饰在线商店:Lamoda.kz
2019/11/19 全球购物
英国手工制作的现代与经典的沙发和床:Love Your Home
2020/09/26 全球购物
财产保全担保书范文
2014/04/01 职场文书
单位消防安全责任书
2014/07/23 职场文书
2015公务员试用期工作总结
2014/12/12 职场文书
房地产销售员岗位职责
2015/04/11 职场文书
安全主题班会教案
2015/08/12 职场文书
Python图片检索之以图搜图
2021/05/31 Python