`

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

    博客分类:
  • PHP
阅读更多

以前追踪过这个问题,但是那个时候工具用的不太好,没看的这么细,这次搞的比较细,修正了偶以前的看法.于是写小文一篇总结一下.

PHP偶尔会爆一下如下 错误Allowed memory size of  xxx bytes exhausted at xxx:xxx (tried to allocate xxx bytes)

不想看原理的,直接跳到最后看总结.

这个报错信息的意思是是说,若ini配置的memory_limit(内存限制) 大于 AG(allocated_memory),就报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
AG(allocated_memory) += rs;
 
if (AG(memory_limit)<AG(allocated_memory)) {
 
    int php_mem_limit = AG(memory_limit);
    AG(allocated_memory) -= rs; 
 
   if (EG(in_execution) &#038;& AG(memory_limit)+1048576 > AG(allocated_memory)){ 
 
        AG(memory_limit) = AG(allocated_memory) + 1048576; 
 
        if (file) {
            zend_error(E_ERROR,"Allowed memory size of %d bytes exhausted
at %s:%d (tried to allocate %d bytes)", php_mem_limit, file, lineno, s);
        } else {
            zend_error(E_ERROR,"Allowed memory size of %d bytes exhausted
(tried to allocate %d bytes)", php_mem_limit, s);
        } 
 
    } else { 
 
        if (file) {
           fprintf(stderr, "Allowed memory size of %d bytes exhausted
at %s:%d (tried to allocate %d bytes)n", php_mem_limit, file, lineno, s);
        } else {
            fprintf(stderr, "Allowed memory size of %d bytes exhausted
(tried to allocate %d bytes)n", php_mem_limit, s);
        } 
 
        exit(1);
    }
}

memory_limit很简单,就是PHP可用的内存..AG(allocated_memory)是什么呢?是不是已经使用的内存,恩,我们用代码验证一下

PHP_FUNCTION(memory_get_usage) {
        RETURN_LONG(AG(allocated_memory));
}

这下就清晰明了,还不懂的,查php手册,看memory_get_usage的说明

到底什么时候设置AG(allocated_memory)呢,具体代码就不贴了,太繁琐,是在emalloc函数中调用了第一段代码,看第一行代码,那里的rs就是每次tried to allocate %d bytes对应的s变量(你要申请的实际空间)的align对齐,具体计算方法:rs = (s+7) & ~0x7,也就是必须是8的倍数,不足则补足,这样做的好处是符合64位机器的要求,可以加速运算,例如 s =1,那么运算出来的rs =8 ,具体的,可以自己用PHP写个函数计算(0×7是16进制写法).

总结:既然知道了怎么回事,就好解决了,在开启 –enable-memory-limit情况下,会出这个错误,把配置文件直接设置memory_limit,或者在代码中设置ini_set(‘memory_limit’, ‘value’)都可以,省事的办法就是设置配置文件(如php.ini)

而且建议开启–enable-memory-limit,若这个不开启,PHP的内存限制就处于”裸跑”状态,可能会出现著名的out of memory错误.

使用脚本语言最大的好处之一就是可利用其拥有的自动垃圾回收机制(释放内存)。你不需要在使用完变量后做任何释放内存的处理,PHP会帮你完成。

当然,我们可以按自己的意愿调用 unset() 函数来释放内存,但通常不需要这么做。

 

不过在PHP里,至少有一种情况内存不会得到自动释放,即便是手动调用 unset()。详情可考:http://bugs.php.net/bug.php?id=33595

问题症状

如果两个对象之间存在着相互引用的关系,如“父对象-子对象”,对父对象调用 unset() 不会释放在子对象中引用父对象的内存(即便父对象被垃圾回收,也不行)。

有些糊涂了?我们来看下面的这段代码:

<?php
class 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()) . "\n";
}
?>
 

运行这段代码,你会看到内存使用率越来越高越来越高,直到用光光。

...
33,551,616
33,551,976
33,552,336
33,552,696
PHP 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 方法可将所有内部的父对象引用全部清除,也就是说可以将这部分本来会溢出的内存释放掉。

以下是“修复后”的代码:

<?php
class 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()) . "\n";
}
?>
 

注意那个新增的 Foo::__destruct()方法,以及在释放对象前对 $foo->__destruct() 的调用。现在这段代码解决了内存使用率一直增加的问题,这么一来,代码就可以很好的工作了。

PHP内核解决方案?

为什么会有内存溢出的发生?我对PHP内核方面的研究并不精通,但可以确定的是此问题与引用计数有关系。

在 $bar 中引用 $foo 的引用计数不会因为父对象 $foo 被释放而递减,这时PHP认为你仍需要 $foo 对象,也就不会释放这部分的内存……大概是这样。

这里确实可以看出我的无知,但大体意思是:一个引用计数没有递减,所以一些内存永远得不到释放。

 

在前面提到的 bugs.php.net 链接中我看到修改垃圾回收的过程将会牺牲极大的性能,因为我对引用计数了解不多,所以我认为这是真的。

 

与其改变垃圾回收的过程,为什么不用 unset() 对内部对象做释放的工作呢?(或者在释放对象的时候调用 __destruct()?)

也许PHP内核开发者可以在此或其他地方,对这种垃圾回收处理机制做出修改。

 

更新:Martin Fjordvald 在评论中提到了一个由 David Wang 为垃圾回收所写的补丁(其实它看起来更像“一整块布”——非常巨大。详情参见此邮件结尾的CVS导出信息。)确实存在(一封邮件),并受到了PHP内核开发成员的关注。问题是这个补丁要不要放到PHP5.3中并未得到太多支持我觉得一个不错的折中方案就是在 unset() 函数中调用对象中的 __destruct() 方法;

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics