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. 这个网页用到了什么技术,<script>标签,还有双大括号{{}}是什么意思

    <#compress> <@override name="title">${brand.name}-商品</@override> <@ov ...

  2. iphonex适配

    这一篇具体适配步骤比较全面 iphonex适配 这一篇图文讲解比较全面 关于H5页面在iPhoneX适配

  3. 【Python】使用python操作mysql数据库

    这是我之前使用mysql时用到的一些库及开发的工具,这里记录下,也方便我查阅. python版本: 2.7.13 mysql版本: 5.5.36 几个python库 1.mysql-connector ...

  4. 【Java并发编程】之一:可重入内置锁

    每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块 ...

  5. QString,string,char* 在utf8和gbk不同编码下的相互转化

    关于编码简介:ascii编码是最开始的编码规则本,里面只收纳了英文.特殊字符.数字等有限字符,采用的是8位一个字节的方式进行编码对照:unicode在ascii码的基础上进行了升级扩展,立志将全世界所 ...

  6. BZOJ 2742: [HEOI2012]Akai的数学作业

    2742: [HEOI2012]Akai的数学作业 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 535  Solved: 226[Submit][S ...

  7. 【BZOJ4991】我也不知道题目名字是什么(线段树)

    [BZOJ4991]我也不知道题目名字是什么(线段树) 题面 BZOJ 题解 对于线段树维护的区间维护以下东西: 区间左(右)端开始(结束)的最长(短)子串的长度 左端右端的值,以及当前区间内的答案 ...

  8. 【django基础之ORM,增删改查】

    一.定义 1.什么是ORM? ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候, ...

  9. Reactor模式,或者叫反应器模式 - 为什么用多路io复用提供吞吐量

    Reactor这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些.通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或 ...

  10. bzoj 3611: [Heoi2014]大工程 && bzoj 2286: [Sdoi2011消耗战

    放波建虚树的模板. 大概是用一个栈维护根节点到当前关键点的一条链,把其他深度大于lca的都弹出去. 每次做完记得复原. 还有sort的时候一定要加cmp!!! bzoj 3611 #include&l ...