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操作XML作为数据库的类
Dec 19 PHP
PHP数组对比函数,存在交集则返回真,否则返回假
Feb 03 PHP
php常用文件操作函数汇总
Nov 22 PHP
Yii不依赖Model的表单生成器用法实例
Dec 04 PHP
php+mysqli使用面向对象方式更新数据库实例
Jan 29 PHP
Symfony核心类概述
Mar 17 PHP
php中static和const关键字用法分析
Dec 07 PHP
详解PHP中的序列化、反序列化操作
Mar 21 PHP
PHP简单实现欧拉函数Euler功能示例
Nov 06 PHP
PHP实现的字符串匹配算法示例【sunday算法】
Dec 19 PHP
PHP使用SMTP邮件服务器发送邮件示例
Aug 28 PHP
tp5使用layui实现多个图片上传(带附件选择)的方法实例
Nov 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
星际争霸教主Flash的ID由来:你永远不会知道他之前的ID是www!
2019/01/18 星际争霸
剧场版动画《PSYCHO-PASS 3 FIRST INSPECTOR》3月27日日本上映!
2020/03/06 日漫
Thinkphp3.2.3分页使用实例解析
2016/07/28 PHP
PHP封装类似thinkphp连贯操作数据库Db类与简单应用示例
2019/05/08 PHP
php输出文字乱码的解决方法
2019/10/04 PHP
PHP执行系统命令函数实例讲解
2021/03/03 PHP
用JS实现的一个include函数
2007/07/21 Javascript
JQuery 引发两次$(document.ready)事件
2010/01/15 Javascript
extjs两个tbar问题探讨
2013/08/08 Javascript
jquery实现瀑布流效果分享
2014/03/26 Javascript
浅析AngularJS中的指令
2016/03/20 Javascript
jQuery实现的自适应焦点图效果完整实例
2016/08/24 Javascript
jQuery为动态生成的select元素添加事件的方法
2016/08/29 Javascript
js中scrollTop()方法和scroll()方法用法示例
2016/10/03 Javascript
JS自定义混合Mixin函数示例
2016/11/26 Javascript
Html5+jQuery+CSS制作相册小记录
2016/12/30 Javascript
JavaScript实现水平进度条拖拽效果
2017/01/18 Javascript
angularjs ui-router中路由的二级嵌套
2017/03/10 Javascript
Bootstrap进度条与AJAX后端数据传递结合使用实例详解
2017/04/23 Javascript
Node+Express+MongoDB实现登录注册功能实例
2017/04/23 Javascript
Bootstrap 3多级下拉菜单实例
2017/11/23 Javascript
详解vue2.0+vue-video-player实现hls播放全过程
2018/03/02 Javascript
js中自定义react数据验证组件实例详解
2018/10/19 Javascript
uni-app从安装到卸载的入门教程
2020/05/15 Javascript
vue中父子组件传值,解决钩子函数mounted只运行一次的操作
2020/07/27 Javascript
vue通过接口直接下载java生成好的Excel表格案例
2020/10/26 Javascript
用实例详解Python中的Django框架中prefetch_related()函数对数据库查询的优化
2015/04/01 Python
详解Python 协程的详细用法使用和例子
2018/06/15 Python
西班牙在线宠物商店:zooplus.es
2017/02/24 全球购物
贝玲妃英国官网:Benefit英国
2018/02/03 全球购物
个人求职简历的自我评价
2013/10/19 职场文书
医院总经理职责
2013/12/26 职场文书
《燕子》教学反思
2014/02/18 职场文书
促销活动方案模板
2014/02/24 职场文书
节电标语大全
2014/06/23 职场文书
2019学生会干事辞职信
2019/06/27 职场文书