诊断内存泄漏 - # 字节的允许内存大小已用尽
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
出于这个问题的目的,我们假设可以想象的最糟糕的意大利面条代码隐藏在$user
或Task
的全局范围内。
哪些工具、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 && 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字节已用尽