PHP对象相互引用的内存溢出实例分析

通常来说使用脚本语言最大的好处之一就是可利用其拥有的自动垃圾回收机制来释放内存。你不需要在使用完变量后做任何释放内存的处理,因为这些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运行原理有所帮助。

(0)

相关推荐

  • php中引用&的用法分析【变量引用,函数引用,对象引用】

    本文实例分析了php中引用&的用法.分享给大家供大家参考,具体如下: php的引用(就是在变量或者函数.对象等前面加上&符号) //最重要就是 删除引用的变量 ,只是引用的变量访问不了,但是内容并没有销毁 在PHP 中引用的意思是:不同的名字访问同一个变量内容. 变量的引用 PHP 的引用允许你用两个变量来指向同一个内容 <?php $a="ABC"; $b =&$a; echo $a;//这里输出:ABC echo $b;//这里输出:ABC $b=&q

  • 浅谈PHP变量作用域以及地址引用问题

    作用域的概念: 在PHP脚本的任何位置都可以声明变量,但是,声明变量的位置会大大影响访问变量的范围.这个可以访问的范围称为作用域. 主要的常用的包括:局部变量.全局变量.静态变量. 1.局部变量:就是在函数内声明的变量,他保存在内存的栈内,所以访问速度很快.仅在函数内有效. 2.全局变量:与局部变量相反,全局变量可以在程序的任何地方访问.只要在变量前面加关键字GLOBAL,就可以将其识别为全局变量.在整个php文件内有效. 3.静态变量:用static修饰只存在于函数作用域的变量,函数执行结束后

  • php引用地址改变变量值的问题

    复制代码 代码如下: <?php $foo = 'Bob'; // 将 'Bob' 赋给 $foo $bar = &$foo; // 通过 $bar 引用 $foo echo $foo.'<br/>'; $bar = "My name is $bar"; // 修改 $bar 变量 echo $bar.'<br/>'; echo $foo.'<br/>'; // $foo 的值也被修改 ?> 输出: Bob My name is

  • PHP中函数内引用全局变量的方法

    先看下面的代码: 复制代码 代码如下: <?php $var1 = "#####"; $var2 = "&&&&&"; function global_references($use_globals) { global $var1, $var2; if (!$use_globals) { $var2 =&$var1; //1 } else { $GLOBALS["var2"] =&$v

  • PHP变量的定义、可变变量、变量引用、销毁方法

    复制代码 代码如下: <?php$long="big_long_variable_name";$$long="PHP";     /* 用存放在变量$long里的字符串作为新变量的变量名,等同于$big_long_variable_name="PHP"; */$short=& $big_long_variable_name;  /* 取变量$big_long_variable_name的值赋给变量$short,此时$short的值为

  • php中引用符号(&)的使用详细介绍

    与C语言中的指针是有差别的.C语言中的指针里面存储的是变量的内容在内存中存放的地址变量的引用. PHP 的引用允许你用两个变量来指向同一个内容 $a="ABC"; $b =&$a; echo $a;//这里输出:ABC echo $b;//这里输出:ABC $b="EFG"; echo $a;//这里$a的值变为EFG 所以输出EFG echo $b;//这里输出EFG 函数的传址调用 传址调用我就不多说了 下面直接给出代码 <?php functio

  • PHP对象递归引用造成内存泄漏分析

    通常来说,如果PHP对象存在递归引用,就会出现内存泄漏.这个Bug在PHP里已经存在很久很久了,先让我们来重现这个Bug,示例代码如下: <?php class Foo { function __construct() { $this->bar = new Bar($this); } } class Bar { function __construct($foo) { $this->foo = $foo; } } for ($i = 0; $i < 100; $i++) { $ob

  • php函数间的参数传递(值传递/引用传递)

    php:函数间的参数传递 1.值传递 复制代码 代码如下: <?php function exam($var1){ $var1++: echo "In Exam:" . $var1 . "<br />"; } $var1 = 1; echo $var1 . "<br />"; exam($var1); echo $var1 . "<br />"; ?> -------------

  • php面向对象全攻略 (三)特殊的引用“$this”的使用

    7.特殊的引用"$this"的使用 现在我们知道了如何访问对象中的成员,是通过"对象->成员"的方式访问的,这是在对 象的外部去访问对象中成员的形式,那么如果我想在对象的内部,让对象里的方法访问本对 象的属性,或是对象中的方法去调用本对象的其它方法这时我们怎么办?因为对象里面的所 有的成员都要用对象来调用,包括对象的内部成员之间的调用,所以在PHP 里面给我提供了 一个本对象的引用$this,每个对象里面都有一个对象的引用$this 来代表这个对象,完成对象

  • PHP对象相互引用的内存溢出实例分析

    通常来说使用脚本语言最大的好处之一就是可利用其拥有的自动垃圾回收机制来释放内存.你不需要在使用完变量后做任何释放内存的处理,因为这些PHP会帮你完成. 当然,我们可以按自己的意愿调用 unset() 函数来释放内存,但通常不需要这么做. 不过在PHP里,至少有一种情况内存不会得到自动释放,即便是手动调用 unset().详情可考PHP官网关于内存泄露的分析:http://bugs.php.net/bug.php?id=33595. 问题症状如下: 如果两个对象之间存在着相互引用的关系,如"父对象

  • Python内存管理实例分析

    本文实例讲述了Python内存管理.分享给大家供大家参考,具体如下: a = 1 a是引用,1是对象.Python缓存整数和短字符串,对象只有一份,但长字符串和其他对象(列表字典)则有很多对象(赋值语句创建新的对象). from sys import getrefcount a=[1,2,3] print(getfrecount(a)) 返回4,当使用某个引用作为参数传给getfrecount时,创建了临时引用,+1. 对象引用对象 class from_obj(object): def __i

  • Java 堆内存溢出原因分析

    前言 任何使用过基于 Java 的企业级后端应用的软件开发者都会遇到过这种低劣.奇怪的报错,这些报错来自于用户或是测试工程师: java.lang.OutOfMemoryError:Java heap space. 为了弄清楚问题,我们必须返回到算法复杂性的计算机科学基础,尤其是"空间"复杂性.如果我们回忆,每一个应用都有一个最坏情况特征.具体来说,在存储维度方面,超过推荐的存储将会被分配到应用程序上,这是不可预测但尖锐的问题.这导致了堆内存的过度使用,因此出现了"内存不够&

  • Java常见内存溢出异常分析与解决

    Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等,而Hotspot jvm的实现中,将堆内存分为了三部分,新生代,老年代,持久带,其中持久带实现了规范中规定的方法区,而内存模型中不同的部分都会出现相应的OutOfMemoryError错误,接下来我们就分开来讨论一下.java.lang.OutOfMemoryError这个错误我相信大部分开发人员都有遇到过,产生该错误的原因大都出于以下原因: JVM内存过小.程序不严密,产生了过多的垃圾. 导致OutOfMemor

  • Go语言共享内存读写实例分析

    本文实例分析了Go语言共享内存读写的方法.分享给大家供大家参考.具体分析如下: 前面分析了Go语言指针运算和内嵌C代码的方法,做了一个Go语言共享内存读写的实验. 先大概说下什么是共享内存.我们知道不同进程见的内存是互相独立的,没办法直接互相操作对方内的数据,而共享内存则是靠操作系统提供的内存映射机制,让不同进程的一块地址空间映射到同一个虚拟内存区域上,使不同的进程可以操作到一块共用的内存块.共享内存是效率最高的进程间通讯机制,因为数据不需要在内核和程序之间复制. 共享内存用到的是系统提供的mm

  • C#编程实现对象与JSON串互相转换实例分析

    本文实例分析了C#编程实现对象与JSON串互相转换的方法.分享给大家供大家参考,具体如下: DoNet2.0 需要借助于Newtonsoft.Json.dll 代码如下: using System; using System.IO; using System.Text; using Newtonsoft.Json; namespace OfflineAcceptControl.UCTools { public class JsonTools { // 从一个对象信息生成Json串 public

  • python中引用与复制用法实例分析

    本文实例讲述了python中引用与复制用法.分享给大家供大家参考.具体分析如下: 在python中,任何不可变对象是传值的,而可变对象是传引用的. 不管是向函数传递参数或者是任何形式的对象复制来说,不可变对象(比如整数,字符串)被真正复制,而可变对象只是复制了一个对他们的引用,即在内存中只有一份对象,而引用两份.   a=b 这样的赋值,就会创建对b的引用,对于象数字和字符串这样的不可变的对象,这种赋值实际是创建了b的一个副本 >>> a='hello' >>> b=a

  • JavaScript变量作用域及内存问题实例分析

    本文实例讲述了JavaScript变量作用域及内存问题.分享给大家供大家参考,具体如下: 学习要点: 1.变量及作用域 2.内存问题 JavaScript的变量与其他语言的变量有很大区别.JavaScript变量是松散型的(不强制类型)本质,决定了它只是在特定时间用于保存特定值的一个名字而已.由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变. 一.变量及作用域 1.基本类型和引用类型的值 ECMAScript变量可能包含两种不同的数据类型的值:

  • JavaScript对象拷贝与Object.assign用法实例分析

    本文实例讲述了JavaScript对象拷贝与Object.assign用法.分享给大家供大家参考,具体如下: 深拷贝与浅拷贝 在 JavaScript 中,对于基本数据类型(undefined.null.boolean.number.string)来说,在变量中存储的就是这个变量本身的值,复制是对值的复制,不存在深浅之说.但C系语言的共同特点中有,存储引用类型(对象),实际中在变量里存的是它的地址.因此对 JavaScript 中的复杂数据类型(object)来说,也会有浅拷贝和深拷贝的概念:浅

随机推荐