诊断内存泄漏 - 允许#bytes的内存大小耗尽

时间:2009-05-11 19:04:01

标签: php memory-leaks

我遇到了可怕的错误消息,可能是经过艰苦的努力,PHP已经耗尽了内存:

  

在第123行的file.php中,####字节的允许内存大小耗尽(尝试分配####字节)

增加限额

如果您知道自己在做什么并希望增加限额,请参阅memory_limit

ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit

小心!你可能只是在解决症状,而不是问题!

诊断泄漏:

错误消息指向一条带有循环的行,我认为该循环正在泄漏或不必要地累积内存。我在每次迭代结束时都打印了memory_get_usage()语句,可以看到数字慢慢增长,直到达到极限:

foreach ($users as $user) {
    $task = new Task;
    $task->run($user);
    unset($task); // Free the variable in an attempt to recover memory
    print memory_get_usage(true); // increases over time
}

出于这个问题的目的,让我们假设可以想象的最糟糕的意大利面条代码隐藏在$userTask的某个地方的全局范围内。

哪些工具,PHP技巧或调试巫毒可以帮助我找到并解决问题?

13 个答案:

答案 0 :(得分:45)

PHP没有垃圾收集器。它使用引用计数来管理内存。因此,最常见的内存泄漏源是循环引用和全局变量。如果你使用一个框架,你会有很多代码需要搜索才能找到它,我担心。最简单的工具是有选择地将调用放到memory_get_usage并将其缩小到代码泄漏的位置。您还可以使用xdebug创建代码跟踪。使用execution tracesshow_mem_delta运行代码。

答案 1 :(得分:11)

在php中有几个可能的内存泄漏点:

  • php本身
  • php extension
  • 你使用的php库
  • 你的PHP代码

如果没有深度逆向工程或php源代码知识,很难找到并修复前3个。对于最后一个,您可以使用二进制搜索来使用memory_get_usage

的内存泄漏代码

答案 2 :(得分:9)

这是我们用来确定哪些脚本在我们的服务器上使用最多内存的技巧。

将以下代码段保存在文件中,例如/usr/local/lib/php/strangecode_log_memory_usage.inc.php

<?php
function strangecode_log_memory_usage()
{
    $site = '' == getenv('SERVER_NAME') ? getenv('SCRIPT_FILENAME') : getenv('SERVER_NAME');
    $url = $_SERVER['PHP_SELF'];
    $current = memory_get_usage();
    $peak = memory_get_peak_usage();
    error_log("$site current: $current peak: $peak $url\n", 3, '/var/log/httpd/php_memory_log');
}
register_shutdown_function('strangecode_log_memory_usage');

通过在httpd.conf中添加以下内容来使用它:

php_admin_value auto_prepend_file /usr/local/lib/php/strangecode_log_memory_usage.inc.php

然后在/var/log/httpd/php_memory_log

分析日志文件

在您的网络用户可以写入日志文件之前,您可能需要touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log

答案 3 :(得分:7)

我注意到在一个旧脚本中有一次,即使在我的foreach循环之后,PHP也会在范围内维护“as”变量。例如,

foreach($users as $user){
  $user->doSomething();
}
var_dump($user); // would output the data from the last $user 

我不确定未来的PHP版本是否已修复此问题,因为我已经看过了。如果是这种情况,您可以在unset($user)行之后doSomething()从内存中清除它。因人而异。

答案 4 :(得分:6)

我最近在一个应用程序上遇到了这个问题,在我收集到的类似情况下。一个在PHP的cli中运行的脚本,它遍历许多迭代。我的脚本依赖于几个底层库。我怀疑某个特定的库是原因,我花了几个小时徒劳地试图在它的类中添加适当的析构方法无济于事。面对一个漫长的转换过程到一个不同的库(可能会出现同样的问题),我想出了一个粗略的解决方案来处理我的问题。

在我的情况下,在linux cli上,我循环了一堆用户记录,并为每一个创建了我创建的几个类的新实例。我决定尝试使用PHP的exec方法创建类的新实例,以便这些进程可以在“新线程”中运行。这是我所指的一个非常基本的样本:

foreach ($ids as $id) {
   $lines=array();
   exec("php ./path/to/my/classes.php $id", $lines);
   foreach ($lines as $line) { echo $line."\n"; } //display some output
}

显然这种方法有局限性,需要注意这种方法的危险性,因为创建兔子工作很容易,但在极少数情况下,它可能有助于克服困难,直到更好的解决方案可以找到,就像我的情况一样。

答案 5 :(得分:6)

我遇到了同样的问题,我的解决方案是用常规替换foreach。我不确定具体细节,但似乎foreach为对象创建了一个副本(或某种新的引用)。使用常规for循环,您可以直接访问该项目。

答案 6 :(得分:3)

我最近注意到PHP 5.3 lambda函数在删除它们时会留下额外的内存。

for ($i = 0; $i < 1000; $i++)
{
    //$log = new Log;
    $log = function() { return new Log; };
    //unset($log);
}

我不知道为什么,但即使在移除该函数后,每个lambda似乎还需要250个字节。

答案 7 :(得分:3)

我建议你查看php手册或添加gc_enable()函数来收集垃圾......这就是内存泄漏不会影响代码的运行方式。

PS:php有一个不带参数的垃圾收集器gc_enable()

答案 8 :(得分:2)

如果您对PHP的说法仅在函数成功后执行GC,则可以将循环内容作为变通方法/实验包装在函数中。

答案 9 :(得分:2)

我遇到的一个大问题是使用create_function。与lambda函数一样,它将生成的临时名称保留在内存中。

内存泄漏的另一个原因(在Zend Framework的情况下)是Zend_Db_Profiler。 如果在Zend Framework下运行脚本,请确保已禁用。 例如,我在我的application.ini中有以下内容:

resources.db.profiler.enabled    = true
resources.db.profiler.class      = Zend_Db_Profiler_Firebug

在此之前运行大约25.000个查询+加载处理,使内存达到了一个很好的128Mb(我的最大内存限制)。

只需设置:

resources.db.profiler.enabled    = false

足以将其保持在20 Mb以下

这个脚本在CLI中运行,但是它实例化了Zend_Application并运行了Bootstrap,所以它使用了&#34;开发&#34;配置。

这确实有助于使用xDebug profiling

运行脚本

答案 10 :(得分:2)

我没有明确提到它,但是xdebug在分析时间和记忆方面表现出色(从2.6开始)。您可以获取它生成的信息并将其传递给您选择的gui前端:webgrind(仅限时间),kcachegrindqcachegrind或其他,它会生成非常有用的调用树和图表,让你找到各种困境的来源。

(qcachegrind)示例: enter image description here

答案 11 :(得分:1)

我对这次谈话有点迟,但我会分享与Zend Framework相关的内容。

在安装php 5.3.8(使用phpfarm)与使用php 5.2.9开发的ZF应用程序一起工作后,我遇到了内存泄漏问题。我发现内存泄漏是在Apache的httpd.conf文件中触发的,在我的虚拟主机定义中,它显示SetEnv APPLICATION_ENV "development"。在评论此行后,内存泄漏停止。我正在尝试在我的php脚本中提出内联解决方法(主要是通过在主index.php文件中手动定义它)。

答案 12 :(得分:0)

我没有在这里看到它,但有一点可能有用,就是使用xdebug和xdebug_debug_zval('variableName')来查看引用计数。

我还可以提供一个php扩展程序的例子:Zend Server的Z-Ray。如果启用了数据收集,则内存使用将在每次迭代时生效,就像垃圾收集已关闭一样。