在PHP中安全地捕获"允许的内存大小耗尽"错误

Mat*_*son 64 php memory

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

它受"允许的内存大小耗尽"错误的影响,但不是通过像ini_set('memory_limit','19T')之类的东西来增加内存限制,我只想返回用户应该尝试别的东西,因为它已经习惯了记忆.

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

Dan*_*ugg 49

正如这个答案所暗示的,你可以register_shutdown_function()用来注册一个可以检查的回调error_get_last().

您仍然需要管理由违规代码生成的输出,无论是由@(闭嘴)运算符还是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';
}
Run Code Online (Sandbox Code Playgroud)

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

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

我建议保持error_reporting()高(-1),并使用(和其他人一样建议)使用set_error_handler()和处理其他所有内容的错误处理ErrorException.

  • 如果这对任何其他人来说都是一个问题,那么值得注意的是register_shutdown_function在调用时会丢失你的调用堆栈.所以你不能真正用它来解决*你的内存错误发生的地方. (4认同)
  • @ChrisRae 如果你启用了 xdebug,xdebug_get_function_stack() 是一个 debug_backtrace 替代方案,它将具有整个跟踪 (3认同)
  • +1 表示面对肆意不正确的一般能力:) (2认同)

Ala*_*blo 34

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 当我读到它时,我认为这听起来完全是愚蠢的 - 我认为"当PHP耗尽内存和调用关机功能时,肯定会发生某种清理,你不必担心......" .我错了.非常感谢你花时间发布这个 - 非常有帮助! (5认同)
  • 分配内存的稍微快一点的方法是“new SplFixedArray(65536);”。在我的系统上,每个空数组元素占用 16 个字节。 (2认同)

小智 7

你可以通过使用这个函数得到进程已经消耗的内存大小memory_get_peak_usage文档位于http://www.php.net/manual/en/function.memory-get-peak-usage.php我认为它会如果您可以在进程几乎达到内存限制之前添加条件来重定向或停止进程,则会更容易.:)


hpa*_*nia 5

虽然@ 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);
Run Code Online (Sandbox Code Playgroud)

完整的示例脚本

<?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);
Run Code Online (Sandbox Code Playgroud)

输出将是:

Start: 349KB
After Reserving: 3426KB
After Unsetting: 349KB
After Reserving again: 3426KB
Start Shut Down: 3420KB
End Shut Down: 344KB
Run Code Online (Sandbox Code Playgroud)

  • 分配内存的稍微快一点的方法是“new SplFixedArray(65536);”。在我的系统上,每个空数组元素占用 16 个字节。 (2认同)