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


      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:

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

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

    Php代码  收藏代码
    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.   
    26. class Bar {  
    27.     function __construct($foo) {  
    28.         $this->foo = $foo;  
    29.     }  
    30.       
    31.     function __destruct() {          
    32.         echo('Bar die --2--<br>');  
    33.     }  
    34. }  
    35.   
    36. for ($i = 0; $i < 100; $i++) {  
    37.     echo memory_get_usage(), "<br>";  
    38.     $obj = new Foo();  
    39.     //注解掉,或放开注解下面这一行,运行一下。结果分别在本文后面。  
    40.     //我们可以发现,如果我们不收尸,那么,PHP主动调用__destruct,则是在程序结束时才执行。  
    41.     //如果我们主动做了,就能及时释放内存。  
    42.     $obj->destroy();  
    43.     unset($obj);  
    44. }  
    45. echo memory_get_usage(), "<br>";  
    46. ?>   

        我们可以发现,当我们注解掉上面这段代码中的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
        其中给出的代码:

    Php代码  收藏代码
    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 ";    
    16. for($i = 0; $i < 10000; $i++) {    
    17.     createRelationship();    
    18. }    
    19. echo 'Peak: ' . number_format(memory_get_peak_usage(), 0, '.', ',') . " bytes ";    
    20. echo 'End: ' . number_format(memory_get_usage(), 0, '.', ',') . " bytes ";   

    输出结果是:

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

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

    Php代码  收藏代码
    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.   
    14. function createRelationship() {    
    15.     $parent = new Node();    
    16.     $child = new Node();    
    17.     $parent->childNodes[] = $child;    
    18.     $child->parentNode = $parent;    
    19.     $parent->destroy();    
    20. }   

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

    Php代码  收藏代码
    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了,但是性能上有一些缺陷。

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

    Php代码  收藏代码
      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.   
      28. class Bar {  
      29.     function __construct($foo) {  
      30.         $this->foo = $foo;  
      31.     }  
      32.       
      33.     function __destruct() {          
      34.         echo('Bar die --2--<br>');  
      35.     }  
      36. }  
      37.   
      38. for ($i = 0; $i < 100; $i++) {  
      39.     echo memory_get_usage(), "<br>";  
      40.     $obj = new Foo();  
      41.     //改用静态函数后,这里只要一行。  
      42.     Foo::destroy($obj);  
      43.   
      44. }  
      45. echo memory_get_usage(), "<br>";  
      46. ?>   
  • 相关阅读:
    SDN大作业
    第06组 Beta版本演示
    SqlServer 将多行字段合并成单行
    C# MD5加密字符串
    Request.IsAjaxRequest()总是返回false
    Mybatis 语句包含单引号的写法
    idea每次启动maven项目都貌似读不到配置
    idea下springboot项目配置文件application.properties不生效的问题
    Ubuntu 设置时区
    SpringBoot 使用MyBatis
  • 原文地址:https://www.cnblogs.com/mako/p/4156982.html
Copyright © 2020-2023  润新知