在 PHP 中安全捕获“允许内存大小耗尽”错误

我有一个将 JSON 返回给客户端的网关脚本。 在脚本中,我使用 Set _ error _ Handler 设置错误处理程序来捕捉错误,并且仍然有一个格式化的返回值。

它容易出现“允许内存大小耗尽”错误,但是我不想使用类似 Ini _ set (‘ memory _ limit’,‘19T’)的内存限制来增加内存限制,我只是想返回用户应该尝试其他内存,因为它使用了大量内存。

有没有什么好的方法来捕捉致命的错误?

50921 次浏览

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

正如 这个答案所建议的那样,您可以使用 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';
}

运行时,输出 Caught at shutdown。遗憾的是,没有引发 ErrorException异常对象,因为致命错误触发脚本终止,随后只能在关闭函数中捕获。

您可以在关闭函数中检查 $error数组,以获得有关原因的详细信息,并作出相应的响应。一个建议是针对 Web 应用程序(在不同的地址,当然也有不同的参数)重新发出请求并返回捕获的响应。

我建议保持 error_reporting()高(值为 -1) ,并使用(就像其他人建议的那样)错误处理与 set_error_handler()ErrorException的其他一切。

如果您需要在发生这种错误时执行业务代码(日志记录、备份上下文以备将来的调试、发送电子邮件等) ,仅仅注册一个关闭函数是不够的: 您应该以某种方式释放内存。

一种解决方案是在某个地方分配一些应急内存:

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;
}

虽然@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