安全地捕获PHP中的“允许的内存大小用尽”错误


69

我有一个网关脚本,它将JSON返回给客户端。在脚本中,我使用set_error_handler捕获错误,并且仍然具有格式化的返回值。

它受到“允许的内存大小用尽”错误的影响,但是与其使用ini_set('memory_limit','19T')之类的方法来增加内存限制,我只是想返回用户应该尝试其他方法的原因,因为它过去经常记忆。

有什么好的方法可以捕获致命错误?

Answers:


53

就像这个答案所暗示的那样,您可以使用register_shutdown_function()注册一个check回调error_get_last()

无论是@shutdown)运算符,还是ini_set('display_errors', false)

ini_set('display_errors', false);

error_reporting(-1);

set_error_handler(function($code, $string, $file, $line){
        throw new ErrorException($string, null, $code, $file, $line);
    });

register_shutdown_function(function(){
        $error = error_get_last();
        if(null !== $error)
        {
            echo 'Caught at shutdown';
        }
    });

try
{
    while(true)
    {
        $data .= str_repeat('#', PHP_INT_MAX);
    }
}
catch(\Exception $exception)
{
    echo 'Caught in try/catch';
}

运行时,输出Caught at shutdown。不幸的是,ErrorException没有抛出异常对象,因为致命错误触发了脚本终止,随后仅在关闭函数中被捕获。

您可以$error在关闭功能中检查阵列以获取有关原因的详细信息,然后做出相应的响应。一个建议可能是针对您的Web应用程序(在不同的地址或使用不同的参数)重新发出请求然后返回捕获的响应。

我建议保持error_reporting()较高的值-1值为),并使用(如其他人所建议的)对set_error_handler()和进行其他所有操作ErrorException


面对肆
无忌

4
万一这对其他人来说是个问题,值得牢记的是,register_shutdown_function在被调用时会丢失您的调用堆栈。所以,你真的不能用它来制定出在那里你的记忆发生错误。
克里斯·雷

1
如果您启用了xdebug,则@ChrisRae是xdebug_get_function_stack(),它是一个debug_backtrace替代方案,它将具有整个跟踪记录
Brad Kent

40

如果您需要在发生此错误时执行业务代码(记录日志,备份上下文以供将来调试,通过电子邮件发送等),则注册关闭功能还不够:您应该以某种方式释放内存。

一种解决方案是在某处分配一些紧急内存:

public function initErrorHandler()
{
    // This storage is freed on error (case of allowed memory exhausted)
    $this->memory = str_repeat('*', 1024 * 1024);

    register_shutdown_function(function()
    {
        $this->memory = null;
        if ((!is_null($err = error_get_last())) && (!in_array($err['type'], array (E_NOTICE, E_WARNING))))
        {
           // $this->emergencyMethod($err);
        }
    });
    return $this;
}

6
我认为当我阅读它时听起来完全是愚蠢的-我认为“肯定会在PHP内存不足和调用shutdown函数之间进行某种清理,而您不必为此担心……” 。我错了 非常感谢您抽出宝贵的时间发布此信息-非常有帮助!
乔尔·考克斯

1
要知道的一件事是,您的支票array (E_NOTICE, E_WARNING)实际上会收到折旧通知书和其他不需要的小问题。在某些情况下,您可能需要重写以删除!!否定并将其替换为in_array($err['type'], array (E_ERROR))
Goose

1
迷人。加工。
Glutexo

2
例如,分配内存的更快方法new SplFixedArray(65536);。每个空数组元素在我的系统上消耗16个字节。
ColinM

棒极了。谢谢!
txmail

8

您可以使用此函数获取进程已消耗的内存大小memory_get_peak_usage文档位于http://www.php.net/manual/zh/function.memory-get-peak-usage.php,我认为这会如果您可以添加一个条件来重定向或停止该进程,而该进程几乎达到了内存限制,则将变得更加容易。:)


确实,这是在错误发生之前捕获错误的一种方法。允许将状态信息保存在正在进行的任务中,甚至在重定向后也可以恢复状态信息。
stefgosselin 2011年

1
+1-我同意,在问题变成错误之前进行管理可能是最好的解决方案(并提供最强大的处理选项),但是,针对致命错误的错误“处理”也有其好处。混合解决方案可能是最好的方法,它可能在可能的情况下管理内存状态,在必要时处理致命问题。
丹·拉格

1
可以,但是脚本内存分配错误通常很难预测,因为PHP通常不会与我们讨论内部内存业务,因此我们对调用外部库,数据库查询的内存成本知之甚少,图像处理或仅使用大型多维数组等。只能猜测在什么确切负载水平下开始“预防性恐慌”。(尽管如此,有时还是很不错的。)
Sz。

我遇到了类似的内存分配问题,但情况有所不同:大小调整为10MB的jpeg上载的图像在RAM上的增长很快超过100MB。我使用以下公式来估算所需的RAM:$ width * $ height * 4 * 1.5 + 1048576 from this site link。我不知道它是否是最佳选择,但它似乎可以很好地工作(为图像实际分配的
〜60MB

7

虽然@ alain-tiemblo解决方案可以完美地工作,但我放置了此脚本来展示如何在对象范围之外的php脚本中保留一些内存。

简洁版本

// memory is an object and it is passed by reference
function shutdown($memory) {
    // unsetting $memory does not free up memory
    // I also tried unsetting a global variable which did not free up the memory
    unset($memory->reserve);
}

$memory = new stdClass();
// reserve 3 mega bytes
$memory->reserve = str_repeat('❤', 1024 * 1024);

register_shutdown_function('shutdown', $memory);

完整的示例脚本

<?php

function getMemory(){
    return ((int) (memory_get_usage() / 1024)) . 'KB';
}

// memory is an object and it is passed by reference
function shutdown($memory) {
    echo 'Start Shut Down: ' . getMemory() . PHP_EOL;

    // unsetting $memory does not free up memory
    // I also tried unsetting a global variable which did not free up the memory
    unset($memory->reserve);

    echo 'End Shut Down: ' . getMemory() . PHP_EOL;
}

echo 'Start: ' . getMemory() . PHP_EOL;

$memory = new stdClass();
// reserve 3 mega bytes
$memory->reserve = str_repeat('❤', 1024 * 1024);

echo 'After Reserving: ' . getMemory() . PHP_EOL;

unset($memory);

echo 'After Unsetting: ' . getMemory() . PHP_EOL;

$memory = new stdClass();
// reserve 3 mega bytes
$memory->reserve = str_repeat('❤', 1024 * 1024);

echo 'After Reserving again: ' . getMemory() . PHP_EOL;

// passing $memory object to shut down function
register_shutdown_function('shutdown', $memory);

输出为:

Start: 349KB
After Reserving: 3426KB
After Unsetting: 349KB
After Reserving again: 3426KB
Start Shut Down: 3420KB
End Shut Down: 344KB

1
例如,分配内存的更快方法new SplFixedArray(65536);。每个空数组元素在我的系统上消耗16个字节。
ColinM
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.