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 常用数组内部函数(Array Functions)介绍
Jun 05 PHP
深入for,while,foreach遍历时间比较的详解
Jun 08 PHP
php截取中文字符串不乱码的方法
Dec 25 PHP
php自定义函数截取汉字长度
May 15 PHP
PHP大转盘中奖概率算法实例
Oct 21 PHP
标准PHP的AES加密算法类
Mar 12 PHP
PHP实现linux命令tail -f
Feb 22 PHP
Laravel学习教程之本地化模块
Aug 18 PHP
PHP 实现手机端APP支付宝支付功能
Jun 07 PHP
php设计模式之状态模式实例分析【星际争霸游戏案例】
Mar 26 PHP
PHP基于ip2long实现IP转换整形
Dec 11 PHP
如何理解PHP核心特性命名空间
May 28 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 for 循环语句使用方法详细说明
2010/05/09 PHP
用js实现的页面关键字密度查询代码
2007/12/27 Javascript
JavaScript入门之事件、cookie、定时等
2011/10/21 Javascript
jquery.idTabs 选项卡使用示例代码
2014/09/03 Javascript
javascript实现图像循环明暗变化的方法
2015/02/25 Javascript
JS+CSS实现自适应选项卡宽度的圆角滑动门效果
2015/09/15 Javascript
jquery插件EasyUI中form表单提交实例分享
2016/01/11 Javascript
第一次接触JS require.js模块化工具
2016/04/17 Javascript
jQuery中常用动画效果函数(日常整理)
2016/09/17 Javascript
利用Console来Debug的10个高级技巧汇总
2018/03/26 Javascript
Vue中Axios从远程/后台读取数据
2019/01/21 Javascript
Vue实现一个图片懒加载插件
2019/03/11 Javascript
vue用BMap百度地图实现即时搜索功能
2019/09/26 Javascript
es6 super关键字的理解与应用实例分析
2020/02/15 Javascript
[01:09:20]NB vs NAVI Supermajor小组赛A组 BO3 第二场 6.2
2018/06/03 DOTA
[34:44]Liquid vs TNC Supermajor 胜者组 BO3 第二场 6.4
2018/06/05 DOTA
Python深入学习之闭包
2014/08/31 Python
Python实现把json格式转换成文本或sql文件
2015/07/10 Python
读写json中文ASCII乱码问题的解决方法
2016/11/05 Python
基于Python和Scikit-Learn的机器学习探索
2017/10/16 Python
Python OpenCV处理图像之滤镜和图像运算
2018/07/10 Python
python处理“
2019/06/10 Python
python使用if语句实现一个猜拳游戏详解
2019/08/27 Python
使用Python获取当前工作目录和执行命令的位置
2020/03/09 Python
python根据用户需求输入想爬取的内容及页数爬取图片方法详解
2020/08/03 Python
详解KMP算法以及python如何实现
2020/09/18 Python
HTML5 canvas实现的静态循环滚动播放弹幕
2021/01/05 HTML / CSS
俄罗斯街头服装品牌:Black Star Wear
2017/03/01 全球购物
迪卡侬中国官网:Decathlon中国
2020/08/10 全球购物
求职意向书
2014/04/01 职场文书
个人借款协议书范本
2014/11/17 职场文书
起诉书格式范文
2015/05/20 职场文书
准备去美国留学,那么大学申请文书应该怎么写?
2019/08/12 职场文书
Python中相见恨晚的技巧
2021/04/13 Python
pytorch中的model=model.to(device)使用说明
2021/05/24 Python
MySQL中TIMESTAMP类型返回日期时间数据中带有T的解决
2022/12/24 MySQL