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表单转换textarea换行符的方法
Sep 10 PHP
使用PHP导出Word文档的原理和实例
Oct 21 PHP
简单的php中文转拼音的实现代码
Feb 11 PHP
[原创]php获取数组中键值最大数组项的索引值
Mar 17 PHP
学习php设计模式 php实现原型模式(prototype)
Dec 07 PHP
YII Framework教程之异常处理详解
Mar 14 PHP
PHP中SERIALIZE和JSON的序列化与反序列化操作区别分析
Oct 11 PHP
PHPCMS手机站伪静态设置详细教程
Feb 06 PHP
PHP中递归的实现实例详解
Nov 14 PHP
php实现根据身份证获取精准年龄
Feb 26 PHP
PHP7生产环境队列Beanstalkd用法详解
May 19 PHP
php使用event扩展的io复用测试的示例
Oct 20 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
多人战的战术与战略
2020/03/04 星际争霸
PHP转换IP地址到真实地址的方法详解
2013/06/09 PHP
Linux下安装PHP MSSQL扩展教程
2014/10/24 PHP
php中动态变量用法实例
2015/06/10 PHP
flexigrid 参数说明
2010/11/23 Javascript
A标签触发onclick事件而不跳转的多种解决方法
2013/06/27 Javascript
使用jquery修改表单的提交地址基本思路
2014/06/04 Javascript
js进行表单验证实例分析
2015/02/10 Javascript
JavaScript中Null与Undefined的区别解析
2015/06/30 Javascript
jQuery实现标题有打字效果的焦点图代码
2015/11/16 Javascript
Nodejs express框架一个工程中同时使用ejs模版和jade模版
2015/12/28 NodeJs
7个jQuery最佳实践
2016/01/12 Javascript
Javascript实现的SHA-256加密算法完整实例
2016/02/02 Javascript
动态更新highcharts数据的实现方法
2016/05/28 Javascript
深入解析桶排序算法及Node.js上JavaScript的代码实现
2016/07/06 Javascript
jQuery搜索框效果实现代码(百度关键词联想)
2021/02/25 Javascript
微信小程序 Page()函数详解
2016/10/17 Javascript
微信小程序 仿猫眼实现实例代码
2017/03/14 Javascript
MUI 解决动态列表页图片懒加载再次加载不成功的bug问题
2017/04/13 Javascript
让bootstrap的carousel支持滑动滚屏的实现代码
2017/11/27 Javascript
vue中rem的配置的方法示例
2018/08/30 Javascript
给localStorage设置一个过期时间的方法分享
2018/11/06 Javascript
使用layui监听器监听select下拉框,事件绑定不成功的解决方法
2019/09/28 Javascript
[01:39]2014DOTA2国际邀请赛 Newbee经理CU专访队伍火力全开
2014/07/15 DOTA
Python getopt模块处理命令行选项实例
2014/05/13 Python
使用python实现个性化词云的方法
2017/06/16 Python
Django 路由系统URLconf的使用
2018/10/11 Python
基于python操作ES实例详解
2019/11/16 Python
python多线程实现代码(模拟银行服务操作流程)
2020/01/13 Python
HTML5 File接口在web页面上使用文件下载
2017/02/27 HTML / CSS
HTML实现代码雨源码及效果示例
2020/02/25 HTML / CSS
Topshop法国官网:英国快速时尚品牌
2018/04/08 全球购物
十佳中学生事迹材料
2014/06/02 职场文书
总结python多进程multiprocessing的相关知识
2021/06/29 Python
Redis实现主从复制方式(Master&Slave)
2022/06/21 Redis
Python使用pandas导入xlsx格式的excel文件内容操作代码
2022/12/24 Python