设为首页 收藏本站
查看: 381|回复: 0

[经验分享] PHP内存溢出 Allowed memory size of 解决办法

[复制链接]

尚未签到

发表于 2017-4-8 10:20:09 | 显示全部楼层 |阅读模式
  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
class Foo {
function __construct() {
$this->bar = new Bar($this);
}
}
class Bar {
function __construct($foo) {
$this->foo = $foo;
}
}
for ($i = 0; $i < 100; $i++) {
$obj = new Foo();
unset($obj);
echo memory_get_usage(), "\n";
}
?>

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

<?php
class Foo {
function __construct() {
//PHP5.3以前的版本,它是一个长生不老药。因为创建了它,引用了自己。
//实际上,只要外部有任一个对象的属性中引用了这个类,这个类和引用它的类都不能被unset
//这就是我们在这里犯下了错。
$this->bar = new Bar($this);
}
function __destruct() {
echo('Foo die --3--<br>');
unset($this->bar);
}
/**
* 因为我们在这里犯下了错,我们要在此收拾残局。所以,添加一个收尸函数
* 这一做法,也就是不要在外面调用__destruct,看上去很不雅观。
* 但增加此函数有一大好处。就是向使用者表明,需要我们主动把它杀掉。而不是等它自动死掉。
* 如果不愿意,你大可以直接在外部调用__destruct
*/
function destroy()
{
echo('destroy --1--<br>');
unset($this->bar);
}
}
class Bar {
function __construct($foo) {
$this->foo = $foo;
}
function __destruct() {        
echo('Bar die --2--<br>');
}
}
for ($i = 0; $i < 100; $i++) {
echo memory_get_usage(), "<br>";
$obj = new Foo();
//注解掉,或放开注解下面这一行,运行一下。结果分别在本文后面。
//我们可以发现,如果我们不收尸,那么,PHP主动调用__destruct,则是在程序结束时才执行。
//如果我们主动做了,就能及时释放内存。
$obj->destroy();
unset($obj);
}
echo memory_get_usage(), "<br>";
?>
  我们可以发现,当我们注解掉上面这段代码中的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
    其中给出的代码:

class Node {  
public $parentNode;  
public $childNodes = array();  
function Node() {  
$this->nodeValue = str_repeat('0123456789', 128);  
}  
}  
function createRelationship() {  
$parent = new Node();  
$child = new Node();  
$parent->childNodes[] = $child;  
$child->parentNode = $parent;  
}
//And then let's review the amount of memory allocated by this script after 10,000 calls to createRelationship function.
echo 'Initial: ' . number_format(memory_get_usage(), 0, '.', ',') . " bytes\n";  
for($i = 0; $i < 10000; $i++) {  
createRelationship();  
}  
echo 'Peak: ' . number_format(memory_get_peak_usage(), 0, '.', ',') . " bytes\n";  
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方法,

class Node {  
public $parentNode;  
public $childNodes = array();  
function Node() {  
$this->nodeValue = str_repeat('0123456789', 128);  
}  
function destroy()  
{  
$this->parentNode = null;  
$this->childNodes = array();  
}  
}  
function createRelationship() {  
$parent = new Node();  
$child = new Node();  
$parent->childNodes[] = $child;  
$child->parentNode = $parent;  
$parent->destroy();  
}

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

function createRelationship() {  
$parent = new Node();  
$child = new Node();  
$parent->childNodes[] = $child;  
$child->parentNode = $parent;  
gc_collect_cycles();  
}

  同时,作者也告诉了我们,调用gc_collect_cycles这个函数,我们不需要额外增加destroy了,但是性能上有一些缺陷。
  我们还可以对代码作进一步改进。那就是改成静态函数。具体如下:

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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-361842-1-1.html 上篇帖子: 利用PHP和PEAR动态创建和编辑TAR文档 下篇帖子: 如何使用PHP DOMDocument创建动态XML文件
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表