============================Allowed memory size of  xxx bytes

以前追踪过这个问题,但是那个时候工具用的不太好,没看的这么细,这次搞的比较细,修正了偶以前的看法

.于是写小文一篇总结一下.

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

allocate xxx bytes)

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

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

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) && 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() 方法;

========================内存溢出解决方案

在做数据统计分析时,经常会遇到大数组,可能会发生内存溢出,这里分享一下我的解决方案。还是用例子来说明这个问题,如下:

假定日志中存放的记录数为500000条,那么解决方案如下:

ini_set(‘memory_limit’,’64M’); //重置php可以使用的内存大小为64M,一般在远程主机上是不能修改php.ini文件的,只能通过程序设置。注:在safe_mode(安全模式)下,ini_set失效

set_time_limit(600);//设置超时限制为6分钟

$farr = $Uarr = $Marr = $IParr = $data = $_sub  = array();

$spt = ”$@#!$”;

$root = ”/Data/webapps/VisitLog”;

$path = $dpath = $fpath = NULL;

$path = $root.”/”.date(“Y-m”,$timestamp);

$dpath = $path.”/”.date(“m-d”,$timestamp);

for($j=0;$j<24;$j++){

$v = ($j < 10) ? ”0″.$j : $j;

$gpath = $dpath.”/”.$v.”.php”;

if(!file_exists($gpath)){

continue;

} else {

$arr = file($gpath);////将文件读入数组中

array_shift($arr);//移出第一个单元-》<?php exit;?>

$farr = array_merge($farr,$arr);

unset($arr);

}

}

if(empty($this->farr)){

echo ”<p><center>没有相关记录!</center></p>”;

exit;

}

while(!empty($farr)){

$_sub = array_splice($farr, 0, 10000); //每次取出$farr中1000个

for($i=0,$scount=count($_sub);$i<$scount;$i++){

$arr = explode($spt,$_sub[$i]);

$Uarr[] = $arr[1]; //vurl

$Marr[] = $arr[2]; //vmark

$IParr[] = $arr[3].” |$nbsp;”.$arr[1]; //IP

}

unset($_sub);//用完及时销毁

}

unset($farr);

这里,不难看出,一方面,我们要增加PHP可用内存大小,另一方面,只要我们想办法对数组进行分批处理,分而治之,将用过的变量及时销毁(unset),一般是不会出现溢出问题的。

另外,为了节省PHP程序内存损耗,我们应当尽可能减少静态变量的使用,在需要数据重用时,可以考虑使用引用(&)。再一点就是:数据库操作完成后,要马上关闭连接;一个对象使用完,要及时调用析构函数(__destruct())。

============================unset销毁变量并释放内存问题

PHP的unset()函数用来清除、销毁变量,不用的变量,我们可以用unset()将它销毁。但是某些时候,用unset()却无法达到销毁变 量占用的内存!我们先看一个例子:

<?php
$s=str_repeat('1',255); //产生由255个1组成的字符串
$m=memory_get_usage(); //获取当前占用内存
unset($s);
$mm=memory_get_usage(); //unset()后再查看当前占用内存
echo $m-$mm;
?>

最后输出unset()之前占用内存减去unset()之后占用内存,如果是正数,那么说明unset($s)已经将$s从内存中销毁(或者说,unset()之后内存占用减少了),可是我在PHP5和windows平台下,得到的结果是:0。这是否可以说明,unset($s)并没有起 到销毁变量$s所占用内存的作用呢?我们再作下面的例子:

<?php
$s=str_repeat('1',256); //产生由256个1组成的字符串
$m=memory_get_usage(); //获取当前占用内存
unset($s);
$mm=memory_get_usage(); //unset()后再查看当前占用内存
echo $m-$mm;
?>

这个例子,和上面的例子几乎相同,唯一的不同是,$s由256个1组成,即比第一个例子多了一个1,得到结果是:272。这是否可以说 明,unset($s)已经将$s所占用的内存销毁了?
通过上面两个例子,我们可以得出以下结论:
结论一、unset()函数只能在变量值占用内存空间超过256字节时才会释放内存空间。

那么是不是只要变量值超过256,使用unset就可以释放内存空间呢?我们再通过一个例子来测试一下:

<?php
$s=str_repeat('1',256); //这和第二个例子完全相同
$p=&$s;
$m=memory_get_usage();
unset($s); //销毁$s
$mm=memory_get_usage();
echo $p.'<br />';
echo $m-$mm;
?>

刷新页面,我们看到第一行有256个1,第二行是0,按理说我们已经销毁了$s,而$p只是引用$s的变量,应该是没有内容了,另 外,unset($s)前后内存占用没变化!现在我们再做以下的例子:

<?php
$s=str_repeat('1',256); //这和第二个例子完全相同
$p=&$s;
$m=memory_get_usage();
$s=null; //设置$s为null
$mm=memory_get_usage();
echo $p.'<br />';
echo $m-$mm;
?>

现在刷新页面,我们看到,输出$p已经是没有内容了,unset()前后内存占用量之差是272,即已经清除了变量占用的内存。本例中的$s=null也 可以换成unset(),如下:

<?php
$s=str_repeat('1',256); //这和第二个例子完全相同
$p=&$s;
$m=memory_get_usage();
unset($s); //销毁$s
unset($p);
$mm=memory_get_usage();
echo $p.'<br />';
echo $m-$mm;
?>

我们将$s和$p都使用unset()销毁,这时再看内存占用量之差也是272,说明这样也可以释放内存。那么,我们可以得到另外一条结论:
结论二、只有当指向该变量的所有变量(如引用变量)都被销毁后,才会释放内存。

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

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

    PHP出现如下错误:Allowed memory size of  xxx bytes exhausted at xxx:xxx (tried to allocate xxx bytes)    关于 ...

  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. POJ 3469.Dual Core CPU 最大流dinic算法模板

    Dual Core CPU Time Limit: 15000MS   Memory Limit: 131072K Total Submissions: 24830   Accepted: 10756 ...

  2. MySQL中的联结表

    使用联结能够实现用一条SELECT语句检索出存储在多个表中的数据.联结是一种机制,用来在一条SELECT语句中关联表,不是物理实体,其在实际的数据库表中并不存在,DBMS会根据需要建立联结,且会在查询 ...

  3. [FreeMind] 绘制思维时遇到的常见问题解决办法

    如何改变节点的摆放方向? 如果是新建节点,选择要放置节点的那一侧,按enter键,或者鼠标右键,插入平行节点即可. 如果是已经建好的节点,可以用ctrl+x, ctrl+v粘贴到另一边,或者选中子节点 ...

  4. muduo 的windows下的编译

    四处寻觅开源代码学习,适合的代码非常稀少,不适合的原因却千奇百怪. 不是使用语言特性过于老旧(c++03) 就是使用的冷僻语法(template<T> enable_share_from_ ...

  5. Python常见错误:IndexError: list index out of range

    用python写脚本查询字典时,在遍历字典时循环到某一项时老是报错   出现这种错误有两种情况: 第1种可能情况 list[index]index超出范围 第2种可能情况 list是空值就会出现 In ...

  6. Java中的几种对象(POJO,PO,DTO,DAO,BO)

    j2ee中,经常提到几种对象(object),理解他们的含义有助于我们更好的理解面向对象的设计思维.     POJO(plain old java object):普通的java对象,有别于特殊的j ...

  7. 使用SpringMVC的@CrossOrigin注解解决跨域请求问题

    跨域问题,通俗说就是用ajax请求其他站点的接口,浏览器默认是不允许的.同源策略(Same-orgin policy)限制了一个源(orgin)中加载脚本或脚本与来自其他源(orgin)中资源的交互方 ...

  8. lower_case_table_names

    http://blog.csdn.net/jesseyoung/article/details/40617031 1 简介    在MySQL中,数据库对应数据目录中的目录.数据库中的每个表至少对应数 ...

  9. kbmmw 的HTTPSmartService 上传文件到服务器端

    前面我写过了 HTTPSmartService 使用介绍,可以参见以前的文章. 前一向有同学问如何在http 页面表单上上传文件.一直没有时间回答,自己简单做了例子, 发现无法实现功能,今天花了一天时 ...

  10. centos7 hive + 远程mysql 搭建笔记

    1.require:java环境,本地可用的hadoop,远程可访问的mysql 2.拷贝hive文件(hive-2.2.1) 3.设置环境变量 export HIVE_HOME=/data/spar ...