我遇到了可怕的错误消息,可能通过艰苦的努力,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技巧或调试巫毒可以帮助我找到并解决问题?
PHP没有垃圾收集器.它使用引用计数来管理内存.因此,最常见的内存泄漏源是循环引用和全局变量.如果你使用一个框架,你会有很多代码需要搜索才能找到它,我担心.最简单的工具是有选择地将调用放入memory_get_usage
并缩小到代码泄漏的位置.您还可以使用xdebug创建代码跟踪.使用执行跟踪和运行代码show_mem_delta
.
这是我们用来确定哪些脚本在我们的服务器上使用最多内存的技巧.
将以下代码段保存在文件中,例如/usr/local/lib/php/strangecode_log_memory_usage.inc.php
:
通过在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
在Web用户可以写入日志文件之前.
3> kingoleg..:php中有几个可能的内存泄漏点:
php本身
php扩展
你使用的PHP库
你的PHP代码
没有深度逆向工程或php源代码知识,很难找到并修复前3个.对于最后一个,您可以使用二进制搜索内存泄漏代码和memory_get_usage
你的答案是关于它可能得到的一般性
4> patcoll..:我注意到在一个旧脚本中有一次,即使在我的foreach循环之后,PHP也会在范围内维护"as"变量.例如,
foreach($users as $user){ $user->doSomething(); } var_dump($user); // would output the data from the last $user我不确定未来的PHP版本是否已修复此问题,因为我已经看过了.如果是这种情况,您可以
unset($user)
在该doSomething()
行之后从内存中清除它.因人而异.
PHP没有范围像C/Java /等的循环/条件.即使在退出循环/条件(通过设计[?])之后,在循环/条件内声明的任何内容仍然在范围内.另一方面,方法/函数按照您的预期进行范围化 - 一旦函数执行结束,所有内容都会被释放.
5> Nate Flink..:我最近在一个应用程序上遇到了这个问题,在我收集到的类似情况下.一个在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 }显然这种方法有局限性,需要注意这种方法的危险,因为创建兔子工作很容易,但在极少数情况下,它可能有助于克服困难,直到找到更好的解决方案,就像我的情况一样.
6> Gunnar Lium..:我遇到了同样的问题,我的解决方案是用常规替换foreach.我不确定具体细节,但似乎foreach为对象创建了一个副本(或某种新的引用).使用常规for循环,您可以直接访问该项目.