诊断内存泄漏 - # 字节的允许内存大小已用尽

Posted

技术标签:

【中文标题】诊断内存泄漏 - # 字节的允许内存大小已用尽【英文标题】:Diagnosing Memory Leaks - Allowed memory size of # bytes exhausted 【发布时间】:2010-10-25 08:58:28 【问题描述】:

我遇到了可怕的错误消息,可能是费力的努力,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 技巧或调试 voodoo 可以帮助我找到并解决问题?

【问题讨论】:

P.S. - 我最近遇到了这种确切类型的问题。不幸的是,我还发现php存在子对象破坏问题。如果取消设置父对象,则不会释放其子对象。必须确保我使用修改后的 unset,其中包括对所有子对象的递归调用 __destruct 等等。详细信息:paul-m-jones.com/archives/262 :: 我正在做类似的事情: function super_unset( $item ) if( is_object( $item ) && method_exists( $item, "__destruct" ) ) $item->__destruct(); 未设置($项目); 【参考方案1】:

我没有看到明确提到它,但xdebug 在分析时间和内存方面做得很好(截至2.6)。您可以获取它生成的信息并将其传递给您选择的 gui 前端:webgrind(仅限时间)、kcachegrind、qcachegrind 或其他,它会生成非常有用的调用树和图表,让您找到你各种困境的根源。

示例(qcachegrind):

【讨论】:

【参考方案2】:

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

我还可以提供一个阻碍 php 扩展的示例:Zend Server 的 Z-Ray。如果启用了数据收集,则每次迭代时内存使用都会激增,就像垃圾收集已关闭一样。

【讨论】:

【参考方案3】:

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

将以下 sn-p 保存在文件中,例如 /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 &amp;&amp; chmod 666 /var/log/httpd/php_memory_log,然后您的网络用户才能写入日志文件。

【讨论】:

【参考方案4】:

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

PS:php 有一个垃圾收集器gc_enable(),它不接受任何参数。

【讨论】:

【参考方案5】:

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

内存泄漏的另一个原因(在 Zend 框架的情况下)是 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,所以它使用了“开发”配置。

它确实有助于使用xDebug profiling 运行脚本

【讨论】:

【参考方案6】:

这次谈话我有点晚了,但我会分享一些与 Zend Framework 相关的内容。

在安装 php 5.3.8(使用 phpfarm)以使用使用 php 5.2.9 开发的 ZF 应用程序后,我遇到了内存泄漏问题。我发现内存泄漏是在 Apache 的 httpd.conf 文件中触发的,在我的虚拟主机定义中,它显示为SetEnv APPLICATION_ENV "development"。在注释掉这一行之后,内存泄漏就停止了。我试图在我的 php 脚本中提出一个内联解决方法(主要是通过在主 index.php 文件中手动定义它)。

【讨论】:

问题说他在 CLI 中运行。这意味着 Apache 根本不参与这个过程。 @Maxime 好点,我没听懂,谢谢。哦,好吧,希望一些随机的 Google 员工会从我留在这里的笔记中受益,因为这个页面是在我尝试解决我的问题时出现的。 检查我对这个问题的回答,也许你也是这样。 您的应用程序应该有不同的配置,具体取决于环境。 "development" 环境通常有一堆其他环境可能没有的日志记录和分析。注释掉这一行只会使您的应用程序使用默认环境,通常是"production""prod"。内存泄漏仍然存在;包含它的代码只是没有在该环境中被调用。【参考方案7】:

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

for ($i = 0; $i < 1000; $i++)

    //$log = new Log;
    $log = function()  return new Log; ;
    //unset($log);

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

【讨论】:

我也是这么说的。从 5.3.10 (#60139) 开始,此问题已得到修复 @KristopherIves,感谢您的更新!你是对的,这不再是一个问题,所以我现在不应该害怕疯狂地使用它们。【参考方案8】:

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

【讨论】:

【参考方案9】:

我最近在一个应用程序上遇到了这个问题,我收集到的情况类似。在 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

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

【讨论】:

【参考方案10】:

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

foreach($users as $user)
  $user->doSomething();

var_dump($user); // would output the data from the last $user 

我不确定未来的 PHP 版本是否修复了这个问题,因为我已经看到了。如果是这种情况,您可以在doSomething() 行之后使用unset($user) 将其从内存中清除。 YMMV。

【讨论】:

PHP 没有像 C/Java/etc 这样的循环/条件。即使退出循环/条件(按设计[?]),在循环/条件内声明的任何内容仍然在范围内。另一方面,方法/函数的作用域正如您所期望的那样——一旦函数执行结束,一切都会被释放。 我认为这是设计使然。它的一个好处是,在循环之后,您可以使用找到的最后一个项目,例如满足特定条件的项目。 你可以unset() 它,但请记住,对于对象,你所做的只是改变你的变量指向的位置——你实际上并没有从内存中删除它。无论如何,一旦超出范围,PHP就会自动释放内存,因此更好的解决方案(就这个答案而言,而不是OP的问题)是使用短函数,这样它们就不会从循环中挂起那个变量了长。 @patcoll 这与内存泄漏无关。这只是数组指针的变化。看看这里:prismnet.com/~mcmahon/Notes/arrays_and_pointers.html 版本 3a。【参考方案11】:

如果您所说的 PHP 只在函数之后执行 GC 是真的,您可以将循环的内容包装在函数中作为解决方法/实验。

【讨论】:

@DavidKullmann 其实我认为我的回答是错误的。毕竟被调用的run()也是一个函数,GC应该在函数结束时发生。【参考方案12】:

PHP 没有垃圾收集器。它使用引用计数来管理内存。因此,最常见的内存泄漏源是循环引用和全局变量。如果你使用一个框架,恐怕你需要大量的代码来寻找它。最简单的方法是有选择地调用memory_get_usage 并将其缩小到代码泄漏的地方。您还可以使用xdebug 创建代码跟踪。使用execution traces 和show_mem_delta 运行代码。

【讨论】:

但请注意...生成的跟踪文件将非常庞大。我第一次在 Zend Framework 应用程序上运行 xdebug 跟踪时,运行并生成了一个多 GB(不是 kb 或 MB ... GB)大小的文件。请注意这一点。 是的,它很重.. 虽然 GB 的声音有点大 - 除非你有一个大脚本。也许尝试只处理几行(应该足以识别泄漏)。另外,不要在生产服务器上安装 xdebug 扩展。 自 5.3 PHP 实际上有一个垃圾收集器。另一方面,内存分析功能已从 xdebug 中删除:( +1 发现了漏洞!一个有循环引用的类!一旦这些引用被取消设置(),对象就会按预期被垃圾收集!谢谢! :) @rinogo 那么您是如何发现泄漏的?你能分享一下你采取了哪些步骤吗?【参考方案13】:

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

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

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

【讨论】:

您的回答与它本可以得到的一般性差不多 遗憾的是,即使是 php 7.2 他们也无法修复核心 php 内存泄漏。您不能在其中运行长时间运行的进程。

以上是关于诊断内存泄漏 - # 字节的允许内存大小已用尽的主要内容,如果未能解决你的问题,请参考以下文章

php - 致命错误:允许的内存大小为 134217728 字节已用尽 [重复]

PHP致命错误:允许的内存大小为134217728字节已用尽

使用 pear :致命错误:允许的内存大小为 134217728 字节已用尽(尝试分配 6144 字节)

Composer 要求内存不足。 PHP致命错误:允许的内存大小为1610612736字节已用尽

允许的内存大小为 134217728 字节已用尽(尝试分配 42 字节)[关闭]

图像迁移脚本 - 允许的 134217728 字节内存大小已用尽 [重复]