诊断内存泄漏 – 允许#字节的内存大小被耗尽

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

在第123行的file.php中允许#### bytes允许的内存大小(试图分配####个字节)

增加限制

如果你知道你在做什么,并想增加限制,请参阅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的技巧,或debugging巫术可以帮助我find并解决这个问题?

PHP没有垃圾收集器。 它使用引用计数来pipe理内存。 因此,最常见的内存泄漏来源是循环引用和全局variables。 如果你使用一个框架,恐怕会有很多的代码来寻找它。 最简单的工具是有select地调用memory_get_usage并将其缩小到代码泄漏的位置。 您也可以使用xdebug来创build代码的跟踪。 用执行跟踪和show_mem_delta运行代码。

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

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

如果没有深入的逆向工程或php源代码知识,就很难find和修复前3个。 对于最后一个,你可以使用二进制searchmemory_get_usage的内存泄漏代码

我注意到有一次在旧脚本中,即使在我的foreach循环之后,PHP也会保持“as”variables的作用。 例如,

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

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

最近我在一个应用程序中遇到了这个问题,根据我收集的情况来看类似的情况。 运行在PHP的cli中的脚本,循环了很多次迭代。 我的脚本依赖于几个底层的库。 我怀疑一个特定的图书馆是原因,我徒劳地花了几个小时试图添加适当的破坏方法,它的类没有用。 面对一个漫长的转换过程到一个不同的图书馆(这可能会有相同的问题),我想出了一个粗糙的工作,在我的情况下,这个问题。

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

 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 } 

显然这种方法有局限性,需要注意这个问题的危险性,因为创build一个兔子工作很容易,但是在一些罕见的情况下,它可能有助于克服困难,直到find更好的解决scheme,就像我的情况一样。

我遇到了同样的问题,我的解决scheme是用一个常规的replaceforeach。 我不确定具体情况,但似乎foreach创build一个副本(或某种程度上新的参考)的对象。 使用常规for循环,可以直接访问该项目。

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

将以下代码片段保存在/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的日志文件

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

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

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

我不知道为什么,但似乎每个lambda需要额外的250个字节,即使在函数被删除后。

如果你所说的关于PHP只是在一个函数为true之后进行GC的话,你可以把循环的内容作为一个解决方法/实验包装在一个函数中。

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

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

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

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

通过设置:

 resources.db.profiler.enabled = false 

保持在20Mb就足够了

而且这个脚本是在CLI中运行的,但是它实例化了Zend_Application并运行了Bootstrap,所以它使用了“开发”configuration。

它真的帮助用xDebug分析运行脚本

我build议你检查PHP手册或添加gc_enable()函数来收集垃圾…这是内存泄漏不会影响你的代码如何运行。

PS:PHP有一个垃圾收集器gc_enable() ,没有参数。

我有点迟到这个对话,但我会分享一些与Zend Framework相关的东西。

安装php 5.3.8(使用phpfarm)与使用php 5.2.9开发的ZF应用程序一起工作后,我有内存泄漏问题。 我发现内存泄漏是在Apache的httpd.conf文件中触发的,在我的虚拟主机定义中,它表示SetEnv APPLICATION_ENV "development" 。 在注释完这行后,内存泄漏停止了。 我想在我的PHP脚本(主要是通过在主index.php文件中手动定义它)内联解决方法。

我没有看到这里提到的,但有一件事可能是有用的是使用xdebug和xdebug_debug_zval('variableName')来查看refcount。

我也可以提供一个PHP扩展的例子:Zend Server的Z-Ray。 如果启用了数据收集function,则每次迭代都会使用内存使用,就像垃圾收集已closures一样。