PHP出现如下错误:Allowed memory size of  xxx bytes exhausted at xxx:xxx (tried to allocate xxx bytes)
    关于这一点,本站点中,http://nodonkey.iteye.com/blog/728223 有所讲述。    
    同时,还有 http://hi.baidu.com/thinkinginlamp/blog/item/e400f819a3caab7cdbb4bd4e.html 此文也是讲这个问题的。
    为什么大家都讲了,这里还要讲,是不是多此一举?其实不是。这个问题的英文原文是在:http://paul-m-jones.com/archives/262
    可以看出,有必要再次总结一下。

如果PHP对象存在递归引用,就会出现内存泄漏。这个Bug在PHP里已经存在很久很久了,先让我们来重现这个Bug:

  1. <?php
  2. class Foo {
  3. function __construct() {
  4. $this->bar = new Bar($this);
  5. }
  6. }
  7. class Bar {
  8. function __construct($foo) {
  9. $this->foo = $foo;
  10. }
  11. }
  12. for ($i = 0; $i < 100; $i++) {
  13. $obj = new Foo();
  14. unset($obj);
  15. echo memory_get_usage(), "\n";
  16. }
  17. ?>

运行以上代码,你会发现,内存使用量本应该不变才对,可实际上却是不断增加,unset没有完全生效。
    我们看到,英文原文中给出的做法是,手工调用析构函数。为什么要这样做呢?
    我们给出一段新的代码,注意一下,代码中的注释对问题的讲解:

  1. <?php
  2. class Foo {
  3. function __construct() {
  4. //PHP5.3以前的版本,它是一个长生不老药。因为创建了它,引用了自己。
  5. //实际上,只要外部有任一个对象的属性中引用了这个类,这个类和引用它的类都不能被unset
  6. //这就是我们在这里犯下了错。
  7. $this->bar = new Bar($this);
  8. }
  9. function __destruct() {
  10. echo('Foo die --3--<br>');
  11. unset($this->bar);
  12. }
  13. /**
  14. * 因为我们在这里犯下了错,我们要在此收拾残局。所以,添加一个收尸函数
  15. * 这一做法,也就是不要在外面调用__destruct,看上去很不雅观。
  16. * 但增加此函数有一大好处。就是向使用者表明,需要我们主动把它杀掉。而不是等它自动死掉。
  17. * 如果不愿意,你大可以直接在外部调用__destruct
  18. */
  19. function destroy()
  20. {
  21. echo('destroy --1--<br>');
  22. unset($this->bar);
  23. }
  24. }
  25. class Bar {
  26. function __construct($foo) {
  27. $this->foo = $foo;
  28. }
  29. function __destruct() {
  30. echo('Bar die --2--<br>');
  31. }
  32. }
  33. for ($i = 0; $i < 100; $i++) {
  34. echo memory_get_usage(), "<br>";
  35. $obj = new Foo();
  36. //注解掉,或放开注解下面这一行,运行一下。结果分别在本文后面。
  37. //我们可以发现,如果我们不收尸,那么,PHP主动调用__destruct,则是在程序结束时才执行。
  38. //如果我们主动做了,就能及时释放内存。
  39. $obj->destroy();
  40. unset($obj);
  41. }
  42. echo memory_get_usage(), "<br>";
  43. ?>

我们可以发现,当我们注解掉上面这段代码中的42行[$obj->destroy();]时,__destruct中的调用被PHP延迟了。即如果PHP对象存在递归引用,PHP并不及时释放内存。即使你使用unset,希望强制释放也没有用。
    我们引用部分运行结果:
99616
99984
100352
100720
101088
Foo die --3--
Bar die --2--
Foo die --3--
Bar die --2--
Foo die --3--
Bar die --2--
Foo die --3--
Bar die --2--
Foo die --3--
Bar die --2--
    可以看出Foo,Bar是在程序结束时死的,并且,Foo先死,Bar后死。这是因为,PHP明了,你们都不需要它们了。
    一旦我们手工调用[$obj->destroy();],由于PHP对象存在递归引用已被破坏,所以,unset就起作用了。
    我们引用部分运行结果:
64256
destroy --1--
Bar die --2--
Foo die --3--
65008
destroy --1--
Bar die --2--
Foo die --3--
65008
destroy --1--
Bar die --2--
Foo die --3--
    可以看出,Foo,Bar是在你调用destroy后立即死亡,内存因而不再增长了。
    此BUG据说是在php5.3中解决了。如果说是在php5.3中可以手工调用gc_collect_cycles这个函数。
    假如是这样,其实与你在class中手工添加destroy让用户调用,没有什么区别。

递归引用会被经常用到。特别是用PHP构建树结构对象。注意英文原文中,用户评论中的链接:http://www.alexatnet.com/node/73
    其中给出的代码:

  1. class Node {
  2. public $parentNode;
  3. public $childNodes = array();
  4. function Node() {
  5. $this->nodeValue = str_repeat('0123456789', 128);
  6. }
  7. }
  8. function createRelationship() {
  9. $parent = new Node();
  10. $child = new Node();
  11. $parent->childNodes[] = $child;
  12. $child->parentNode = $parent;
  13. }
  14. //And then let's review the amount of memory allocated by this script after 10,000 calls to createRelationship function.
  15. echo 'Initial: ' . number_format(memory_get_usage(), 0, '.', ',') . " bytes\n";
  16. for($i = 0; $i < 10000; $i++) {
  17. createRelationship();
  18. }
  19. echo 'Peak: ' . number_format(memory_get_peak_usage(), 0, '.', ',') . " bytes\n";
  20. echo 'End: ' . number_format(memory_get_usage(), 0, '.', ',') . " bytes\n";

输出结果是:

Initial: 327,336 bytes  
Peak: 35,824,176 bytes  
End: 35,823,328 bytes

作者建议的方式就是:增加destroy方法,

  1. class Node {
  2. public $parentNode;
  3. public $childNodes = array();
  4. function Node() {
  5. $this->nodeValue = str_repeat('0123456789', 128);
  6. }
  7. function destroy()
  8. {
  9. $this->parentNode = null;
  10. $this->childNodes = array();
  11. }
  12. }
  13. function createRelationship() {
  14. $parent = new Node();
  15. $child = new Node();
  16. $parent->childNodes[] = $child;
  17. $child->parentNode = $parent;
  18. $parent->destroy();
  19. }

另外作者还给出了php5.3中的解决方法:使用php5.3中的新函数,手工调用gc_collect_cycles这个函数。

  1. function createRelationship() {
  2. $parent = new Node();
  3. $child = new Node();
  4. $parent->childNodes[] = $child;
  5. $child->parentNode = $parent;
  6. gc_collect_cycles();
  7. }

同时,作者也告诉了我们,调用gc_collect_cycles这个函数,我们不需要额外增加destroy了,但是性能上有一些缺陷。

我们还可以对代码作进一步改进。那就是改成静态函数。具体如下:

    1. <?php
    2. class Foo {
    3. function __construct() {
    4. //PHP5.3以前的版本,它是一个长生不老药。因为创建了它,引用了自己。
    5. //实际上,只要外部有任一个对象的属性中引用了这个类,这个类和引用它的类都不能被unset
    6. //这就是我们在这里犯下了错。
    7. $this->bar = new Bar($this);
    8. }
    9. function __destruct() {
    10. echo('Foo die --3--<br>');
    11. unset($this->bar);
    12. }
    13. /**
    14. * 因为我们在这里犯下了错,我们要在此收拾残局。所以,添加一个收尸函数
    15. * 这一做法,也就是不要在外面调用__destruct,看上去很不雅观。
    16. * 但增加此函数有一大好处。就是向使用者表明,需要我们主动把它杀掉。而不是等它自动死掉。
    17. * 如果不愿意,你大可以直接在外部调用__destruct
    18. * 实际上,这样做以后,也就不用清理内存时,写两行代码,一行代码就够了。
    19. */
    20. static function destroy($obj)
    21. {
    22. echo('destroy --1--<br>');
    23. unset($obj->bar);
    24. unset($obj);
    25. }
    26. }
    27. class Bar {
    28. function __construct($foo) {
    29. $this->foo = $foo;
    30. }
    31. function __destruct() {
    32. echo('Bar die --2--<br>');
    33. }
    34. }
    35. for ($i = 0; $i < 100; $i++) {
    36. echo memory_get_usage(), "<br>";
    37. $obj = new Foo();
    38. //改用静态函数后,这里只要一行。
    39. Foo::destroy($obj);
    40. }
    41. echo memory_get_usage(), "<br>";
    42. ?>

PHP内存溢出 Allowed memory size of 解决办法的更多相关文章

  1. PHP内存溢出Allowed memory size of 解决办法

    PHP内存溢出Allowed memory size of 解决办法 博客分类: php   ============================Allowed memory size of  x ...

  2. (转载)PHP的内存限制 Allowed memory size of 134217728 bytes exhausted (tried to allocate 1099 bytes) in

    (转载)http://blog.csdn.net/beyondlpf/article/details/7794028 Fatal error: Allowed memory size of 13421 ...

  3. PHP的内存限制 Allowed memory size of 134217728 bytes exhausted (tried to allocate 1099 bytes) in

    Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 1099 bytes) in   Fa ...

  4. Allowed memory size of 134217728 bytes exhausted解决办法(php内存耗尽报错)【简记】

    报错: PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 72 bytes) i ...

  5. Fatal error: Allowed memory size of 524288000 bytes exhausted (tried to allocate 64 bytes) in D

    Fatal error: Allowed memory size of 524288000 bytes exhausted (tried to allocate 64 bytes) in D 从数据库 ...

  6. Fatal error: Allowed memory size of 8388608 bytes exhausted

    这两天安装bugfree,更换了一个数据量较大的库,结果打开bug详情页要么是空白页,要么就报如题的错误,错误信息还包括C:\wamp\www\bugfree\Include\Class\ADOLit ...

  7. 一次apk打开时报内存溢出错误,故写下内存溢出的各种原因和解决方法

    原转载:https://blog.csdn.net/cp_panda_5/article/details/79613870 正文内容: 对于JVM的内存写过的文章已经有点多了,而且有点烂了,不过说那么 ...

  8. PHPExcel 报 Allowed memory size of 8388608 byte

    使用 phpExcel 报 Allowed memory size of 8388608 bytes exhausted 错误,原因是php页面消耗的最大内存默认是为 8M (在PHP的ini件里可以 ...

  9. Allowed memory size of 134217728 bytes exhausted

    错误信息: Allowed memory size of 134217728 bytes exhausted (tried to allocate 65015808 bytes) 由于报错信息和数据库 ...

随机推荐

  1. Sametime SDK

    1,Sametime Server A.Sametime includes many server applications, which collectively provide the capab ...

  2. 【BZOJ2281】【Sdoi2011】黑白棋 解题报告

    [BZOJ2281][Sdoi2011]黑白棋 Description ​ 小A和小B又想到了一个新的游戏. ​ 这个游戏是在一个\(1\)*\(n\)的棋盘上进行的,棋盘上有\(k\)个棋子,一半是 ...

  3. 单点登录(三)-----实战-----cas server 源码下载和部署

    我们在上一篇文章中使用的是4.0版本的cas,4.0版本的有发布好的war包可以直接使用,那如果我们要使用更新的版本怎么办呢? 就需要下载源码自己编辑打包了. 步骤如下: 版本选择 我们在cas的gi ...

  4. Java虚拟机加载类

    Java虚拟机是如何加载Java类的 Java虚拟机加载Java类总共需要经过3步:加载-----链接-----初始化.Java语言的类型可以分为两大类:基本类型和引用类型.基本类型是有Java虚拟机 ...

  5. java线程池赏析

    1.线程池的顶级接口(Executor) 线程池的顶级接口(jdk > 1.5).仅仅定义了方法execute(Runnable). 该方法接收一个Runnable实例,用来执行一个任务,该任务 ...

  6. Andorid APK反逆向

    Andorid APK反逆向解决方案---梆梆加固原理探寻http://blog.csdn.net/androidsecurity/article/details/8892635 Android AP ...

  7. 解题:CQOI 2013 和谐矩阵

    题面 踩踩时间复杂度不正确的高斯消元 首先可以发现第一行确定后就可以确定整个矩阵,所以可以枚举第一行的所有状态然后$O(n)$递推检查是否合法 $O(n)$递推的方法是这样的:设$pre$为上一行,$ ...

  8. MySQL 第七篇:视图、触发器、事务、存储过程、函数

    一 视图 视图是一个虚拟表(非真实存在),其本质是[根据SQL语句获取动态的数据集,并为其命名],用户使用时只需使用[名称]即可获取结果集,可以将该结果集当做表来使用. 使用视图我们可以把查询过程中的 ...

  9. 用ladon框架封装Python为Webservice接口以及调用接口的方法

    一.用ladon框架封装Python为Webservice接口 功能实现的同时,希望将接口开放给别人,而封装python接口的一个再简单不过的框架Ladon,而且提供不同的协议,包括SOAP和Json ...

  10. UESTC--1548

    题目:Easy math 原题链接:http://acm.uestc.edu.cn/problem.php?pid=1548 分析:费马小定理的应用. #include<cstdio> # ...