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

Mik*_*e B 94 php memory-leaks

我遇到了可怕的错误消息,可能通过艰苦的努力,PHP内存不足:

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

增加限制

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

ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
Run Code Online (Sandbox Code Playgroud)

谨防!你可能只是解决症状而不是问题!

诊断泄漏:

错误消息指向一条带有循环的行,我认为该循环正在泄漏或不必要地累积内存.我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
}
Run Code Online (Sandbox Code Playgroud)

对于这个问题的目的,让我们假设最坏的面条代码可以想象在全球范围内的某处藏匿在$userTask.

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

tro*_*skn 47

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

  • 由于5.3 PHP实际上有一个垃圾收集器.另一方面,内存分析功能已被删除xdebug :( (30认同)
  • 但请注意......生成的跟踪文件将是ENORMOUS.我第一次在Zend Framework应用程序上运行xdebug跟踪时,需要花费很长时间来运行并生成一个多GB(不是kb或MB ... GB)大小的文件.请注意这一点. (3认同)
  • +1发现泄漏!一个有循环引用的类!一旦这些引用被取消(),对象就像预期的那样被垃圾收集了!谢谢!:) (3认同)

Qui*_*ant 10

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

将以下代码段保存在文件中,例如/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');
Run Code Online (Sandbox Code Playgroud)

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

php_admin_value auto_prepend_file /usr/local/lib/php/strangecode_log_memory_usage.inc.php
Run Code Online (Sandbox Code Playgroud)

然后分析日志文件 /var/log/httpd/php_memory_log

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


kin*_*leg 9

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

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

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

  • 你的答案是关于它可能得到的一般性 (86认同)
  • 遗憾的是,即使是 php 7.2 他们也无法修复核心 php 内存泄漏。您不能在其中运行长时间运行的进程。 (2认同)

pat*_*oll 7

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

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

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

  • PHP没有范围像C/Java /等的循环/条件.即使在退出循环/条件(通过设计[?])之后,在循环/条件内声明的任何内容仍然在范围内.另一方面,方法/函数按照您的预期进行范围化 - 一旦函数执行结束,所有内容都会被释放. (13认同)

Nat*_*ink 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
}
Run Code Online (Sandbox Code Playgroud)

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


Gun*_*ium 6

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


小智 5

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

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