致命错误: 内存不足,但我有足够的内存(PHP)

72

由于我的问题越来越长,我决定重新编写整个问题,使其更好、更短。

我在8GB内存的专用服务器上运行我的网站。我完全意识到我需要提高php.ini设置中的内存限制。我已将它从128M设置为256M并设置为-1。但问题仍然存在。

致命错误:内存不足(分配了786432)(尝试分配24576字节)在D:\www\football\views\main.php的第81行

内存不足没有意义,因为它说只有分配了786432字节,还需要24576字节。

786432字节只有768KB,相当小。

提示

  • 错误发生在非常随机的行上。它不总是在第81行出错。
  • 在高峰时期,Apache只占用大约500MB的内存。我还有6GB的剩余空间。
  • 没有无限循环。
  • 脚本占用1042424字节。从echo memory_get_peak_usage();获取此数字
  • MySQL的结果集很小(最多12行,纯文本,无blob数据)
  • 重要)如果每两天重新启动一次Apache,则错误会消失。通常发生在Apache运行超过2天时。
  • 我已经包括了脚本分析,您可以在此处获取它here
  • 这个专用服务器纯粹用于运行一个网站。这个网站是一个高流量网站,每分钟平均有1000个访问者。在高峰期,将有1700至2000名访问者同时访问。

服务器规格

操作系统:Windows 2008 R2 64位
CPU:英特尔Core i5-4核
RAM:8 GB
Apache 2.2
PHP 5.3.1
存储:2 x 1 TB硬盘
带宽:每月10TB

解决方案

我终于成功调整和解决了问题,我想在这里分享一下我所做的改进:

  1. favicon.ico丢失,破坏了我的路由引擎。虽然我的路由引擎很小,但通过包含favicon.ico,它有助于减少内存使用,避免运行我的路由引擎。我的网站大部分部分都有它,但我忘记为这个新部分放置它。
  2. 限制MaxRequestPerChild有帮助。在我的其他专用服务器上,我有我的MaxRequestPerChild受限。对于这台服务器,我将其设置为0。我一直认为每个脚本都是隔离的。假设我的脚本需要800KB才能运行。完成后,Apache或PHP应该释放800KB内存。似乎它不起作用。限制MaxRequestPerChild可以通过在限制MaxRequestPerChild之后创建新进程并使旧进程死亡来防止内存泄漏。这是我的新设置。

ThreadsPerChild      1500
MaxRequestsPerChild  10000 
  • ob_flush(); 能稍微减少一点内存占用。虽然帮助不大,但每一点优化都有所帮助。

  • 我使用了xdebug,这是其他回答者建议的,我从未使用过。我必须说这是个好工具,我已经优化了一些东西,让它运行得更快了。
  • 我禁用了一些不必要的 Apache 模块。我正在逐个禁用并留出几天进行测试,以确保它完美地工作后再禁用下一个。现在我已经禁用了所有不必要的 PHP 扩展。
  • 我的服务器上大部分脚本都是传统方式编写的(没有模板、数据库层、纯 PHP、HTML 和 mysql_* 函数)。老实说,它们运行得非常快且内存占用极小。但是,随着网站变得越来越长,维护脚本就不太容易了。我试图将网站的某些部分转换为适当的框架(我的自制迷你框架)。我之所以使用自己的框架,是因为它很小(整个框架只有 3KB,并且仅包含我需要的内容)。
  • 切换到 IIS7.5 完全解决了这个问题

  • 14
    我闻到了无限循环的味道。 :) PHP 的版本是多少?第81行的代码怎么样? - Jared Drake
    3
    绝对值得给 Leigh 点赞。 - Jared Drake
    7
    即使某个方法或变量在70%的情况下有效,也并不意味着一些输入或变量就不会导致火车脱轨并杀死村民。我相当肯定这列火车是一个无限循环。 :D - Jared Drake
    2
    我记得曾经遇到过类似的问题,那是因为我使用了循环来使用Doctrine将一些数据插入数据库,而每次迭代时,它都会在数据库中打开一个游标! - Anas
    3
    让我们稍微跳出常规思维。您的Apache进程是否有任何内存限制?可能有ulimit生效(也许是www用户或PHP脚本所有者,例如.profile)?当在Apache模块中停止工作时,您是否尝试在CLI中运行脚本?您使用任何缓存机制PHP模块吗?您使用suhosin或类似的加固模块吗? - PhilMasteG
    显示剩余20条评论
    20个回答

    34

    我遇到了与服务器使用交换空间时死机的同类问题。这是因为mod_php从不释放内存。因此,Apache进程会不断增长,最终达到Apache或PHP的内存限制,或者,如果没有限制,则使服务器崩溃。

    重新启动Apache可以生成新的轻量级进程,但随着它们运行PHP脚本的时间增长,它们会增长直到出现问题。

    解决方法是使Apache在服务一定数量的查询后杀死进程,以便它创建新的进程(有相关问题),将MaxRequestsPerChild配置选项减少到例如100(默认值为1000)。

    当然,这可能会降低服务器的性能,因为杀死和生成新的进程需要资源,但至少它保持了网站的工作。您可能会想提高正在运行的进程的数量以保持性能,但请确保PHP(或Apache)内存限制x最大进程数未超过服务器的物理RAM。

    这是我的经验,希望能对您有所帮助。


    我现在使用XAMPP,但是遇到了这个错误。如果我使用服务器,是否仍然会遇到这种问题?根据您的回答,我认为我遇到错误的原因是我的内存无法处理所有内容,但如果使用具有更高内存的服务器,这个问题就可以解决了,如果我理解有误,请纠正我。 - Brownman Revival
    MaxRequestsPerChild默认为10000,实际上看起来像http://httpd.apache.org/docs/2.2/mod/mpm_common.html#maxrequestsperchild。 - blamb

    15

    首先,memory_get_peak_usage()在这里并没有帮助。它只会返回已分配的内存量,而这正是导致错误的数量。

    memory_get_usage将返回调用时正在分配的活动内存量。

    ini_set('memory_limit', '256M');将设置PHP在系统内存上的最大占用。如果在768K处出现OOM,则增加不会解决问题。

    没有指示你使用的PHP版本,但我建议立即升级。存在几个错误,其中Zend的内存管理器无法释放内存,这将导致完全相同的问题。

    您的本地服务器和生产服务器是否运行相同版本的操作系统、长位数和PHP版本?答案将是否定的。

    如果与Windows malloc()问题无关,因为它是子域名,可能位于VirtualHost内,并且仅分配768k,这几乎听起来像是操作系统问题。

    在访问脚本时从命令提示符运行tasklist。您是否看到额外的Apache线程或进程中的内存使用率急剧上升?

    最后一个想法是,在每次循环表格行/列之后运行flush()和/或ob_flush();。这应该清除缓冲区,并在发生问题的情况下节省一些内存。


    1
    如果您的文件是Zend编码的,那可能就是问题所在。Zend编码的文件不会像它们应该的那样释放内存(即使您取消设置变量)。我有一些脚本,当处理大文件时会持续耗尽内存,但只有在脚本使用Zend编码时才会出现这种情况(使用未编码版本则每次都能完美运行)。 - msEmmaMays
    将ZendOpcache添加到PHP 5.4.13 w / php-fpm和fcgi会导致我在某些页面上出现内存限制错误。感谢Zend提示。 - Kenneth Benjamin

    8

    首先,建议升级 PHP 版本到 5.4 或以上,因为对于一些应用程序来说,它的速度提升了多达 50%。此外,新版本修复了许多内存泄露问题。详情请参见以下基准测试:http://news.php.net/php.internals/57760


    1
    如果一切都失败了,升级到PHP 5.4+将是我的最后选择。谢谢。 - invisal

    7
    请注意错误为内存不足而非允许的内存大小[..]已用尽
    因此,内存泄漏出现在系统的其他地方。可能是MySQL服务器在执行大查询后使用了大量系统内存,导致Apache/PHP无法获得物理内存和交换空间。
    这应该可以解释为什么错误总是发生在相同的行(和/或在相同的脚本)中。

    在我的情况下,MySQL 内存不足 - 它正在运行于分离的主机上。 - brablc

    6
    安装xdebug并启用分析器触发器。生成一个分析器文件,如果您仍然无法确定问题的源头,请发布cachegrind文件。
    编辑:当然是在内存泄漏发生的页面上的分析器文件!

    1
    说实话,我从未使用过xdebug,但我会尝试一下,并稍后给您反馈。 - invisal
    如果您能够逐步发布“从开始到结束如何执行该过程”的说明,那将非常好。 - T.Todua

    5
    致命错误:内存耗尽(已解决)
    我曾遇到类似的问题,多个月都没找到解决方法。最终在apache文件夹(例如\apache\conf\extra)中检查到了控制apache内存分配的文件。这个文件名叫httpd-mpm。你需要增加MaxMemFree的大小,将其从2048增加到更高的值,我将第一个MaxMemFree设置为10000(IfModule !mpm_netware_module),第二个MaxMemFree设置为5000(IfModule mpm_netware_module)。

    这些操作解决了我的问题。希望对你有所帮助。


    4
    我猜测你可能没有编辑正确的php.ini文件或者你没有重新启动PHP和/或web服务器。
    在你的文档根目录中创建一个phpinfo.php页面,内容为<?php phpinfo();,以确保你正在更改正确的php.ini文件。除了web服务器正在使用的php.ini文件的位置外,它还将说明允许的最大脚本内存。
    接下来,我建议在你的页面上添加一些堆栈跟踪,以便查看导致此问题的事件链。以下函数将捕获致命错误并提供更多有关发生情况的信息。
    register_shutdown_function(function()
    {
        if($error = error_get_last())
        {
            // Should actually log this instead of printing out...
            var_dump($error);
            var_dump(debug_backtrace());
        }
    });
    

    个人而言,自从离开缓慢老旧的 Apache 后,多年来我一直使用 Nginx + PHP-FPM。


    1
    首先,我完全意识到我需要重新启动Apache才能使我的PHP设置生效。我已经包含了由xDebug生成的cachegrind.out文件。我的网站是一个高流量网站,平均每分钟有1,000个访问者。 - invisal
    1
    @invisal,我不怀疑你是一个非常有能力的开发者 - 但有时候简单的事情会被忽视。你确定你正在编辑正确的 php.ini 文件了吗? - Xeoncross
    1
    我已经通过phpinfo()进行了双重检查。现在我得到了正确的设置。我正在努力减少内存使用量。(希望这能解决问题) - invisal

    3

    嘿,我在我的服务器上也遇到了同样的问题。我只是改变了以下内容:

    php.ini更改为...

    memory_limit = 128M
    

    并将其添加到httpd.conf文件中

    RLimitMEM 1073741824 2147483648
    

    并重启apache,我已经解决了错误:


    3

    可能是MySQL和打开的连接数有关的问题,这就是为什么每隔几天重启时会自动解决。它们是否在脚本关闭时自动关闭?


    1
    我只重启Apache来解决问题。脚本不经常调用MySQL,因为它们大多数都被缓存在“memcached”中。 - invisal

    2
    仅作简要回顾(我在离原问题相当远的地方添加了这个答案):
    • PHP 无法分配看似很小的内存
    • 错误发生时的当前内存使用量加上请求的数量仍小于当前内存限制
    • 系统在此时有6GB可供PHP使用
    • 由于重新启动Apache可以解决问题,所以是Apache阻止PHP使用内存
    如果这些都是有效的,那么唯一可能的解释是6GB非常碎片化,但我认为这有点不太可能。您没有说PHP是如何从Apache调用的 - mod_php?fpm?Fcgi?
    我会从检查上述每个谓词开始 - 特别是空闲内存一个。您怎么知道在错误发生时有6GB可用?更可能的原因是存在内存泄漏,而您没有发现它。
    您没有提供有关如何配置Apache的任何详细信息;我还会尝试减少MaxRequestsPerChild和MaxMemFree。(我不太熟悉工作线程的Apache,其中将逐个应用此设置 - 您确实需要每个进程的限制)。如果您提供了来自Apache配置的核心设置,那么也许我们可以提出进一步的建议。
    除非您广泛使用Ajax,请确保keepalive时间为2或更少。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接