【问题标题】:Safely catch a 'Allowed memory size exhausted' error in PHP安全地捕获 PHP 中的“允许的内存大小已耗尽”错误
【发布时间】:2012-01-16 10:44:07
【问题描述】:

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

它会出现“允许的内存大小已用尽”错误,但我不想使用 ini_set('memory_limit', '19T') 之类的东西来增加内存限制,我只想返回用户应该尝试其他方法,因为它使用了很多内存。

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

【问题讨论】:

    标签: php memory


    【解决方案1】:

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

    【讨论】:

    • 确实,这是一种在错误发生之前捕获错误的方法。允许保存正在进行的任务的状态信息,甚至可能在重定向后恢复它。
    • +1 -- 我同意,在问题变成错误之前对其进行管理可能是最好的解决方案(并产生最强大的处理选项)但是,错误“处理”对于致命错误也有它的好处。一种混合解决方案,在可能的情况下管理内存状态,在必要时处理致命事件,可能是最好的方法。
    • 当然,但是脚本内存分配错误通常很难预测,因为 PHP 通常不会与我们讨论它的内部内存业务,所以我们对内存将是什么知之甚少调用外部库、数据库查询、图像处理或仅使用大型多维数组等的成本。在什么确切的负载级别开始“预防性恐慌”,只能猜测。 (尽管如此,有时还是不错的。)
    • 我遇到了类似的内存分配问题,但情况不同:上传的图像调整大小为 10MB 的 jpeg 快速增长到 RAM 上的 100MB 以上。我使用这个公式来计算所需的 RAM:$width * $height * 4 * 1.5 + 1048576 来自该站点link。我不知道它是否是最佳的,但它似乎运行良好(大约 70MB 计算为图像实际分配的大约 60MB)
    【解决方案2】:

    正如this answer 建议的那样,您可以使用register_shutdown_function() 注册一个回调,该回调将检查error_get_last()

    您仍然需要管理由违规代码生成的输出,无论是由 @ (shut up) 运算符还是 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 异常对象没有被抛出,因为致命错误触发了脚本终止,随后仅在关闭函数中捕获。

    您可以查看shutdown函数中的$error数组了解具体原因,并做出相应响应。一种建议可能是针对您的 Web 应用程序重新发出请求(在不同的地址,或者当然使用不同的参数)并返回捕获的响应。

    我建议保持 error_reporting() 高(-1 的值),并使用(正如其他人所建议的)错误处理 @987654332 @和ErrorException

    【讨论】:

    • +1 表示面对肆意错误的一般能力:)
    • 如果这对其他人来说是个问题,请记住 register_shutdown_function 在调用时会丢失您的调用堆栈。所以你不能真正用它来计算出你的内存错误发生在哪里
    • @ChrisRae 如果你启用了 xdebug,xdebug_get_function_stack() 是一个 debug_backtrace 替代方案,它将拥有整个跟踪
    • 我摸不着头脑,我有一个类方法(laravel),我在函数的第一行调用shutdown。内存耗尽,但函数永远不会被调用,也不会回显caught at shutdown。在尝试捕获行之前,我复制了上面粘贴的确切代码。有什么帮助吗?如果我将它放在根文件的 index.php 中,那么它可以工作
    【解决方案3】:

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

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

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

    【讨论】:

    • 当我读到它时,我认为这听起来完全愚蠢 - 我认为“在 PHP 内存不足和调用关闭函数之间肯定会发生某种清理,而你不会有担心那个……”。我错了。非常感谢您抽出宝贵时间发布此内容 - 非常有帮助!
    • 要知道的一点是,您的支票array (E_NOTICE, E_WARNING) 实际上会发现折旧通知和其他不需要的小问题。在某些情况下,您可能需要重写以删除 !否定并将其替换为in_array($err['type'], array (E_ERROR))
    • 令人着迷。工作。
    • 一种稍快的分配内存的方法是例如new SplFixedArray(65536);。在我的系统上,每个空数组元素占用 16 个字节。
    • 惊人的和真棒。谢谢!
    【解决方案4】:

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

    【讨论】:

    • 一种稍快的分配内存的方法是例如new SplFixedArray(65536);。在我的系统上,每个空数组元素占用 16 个字节。
    猜你喜欢
    • 2015-11-25
    • 2017-12-13
    • 1970-01-01
    • 2013-10-21
    • 1970-01-01
    • 2018-08-04
    • 1970-01-01
    • 1970-01-01
    • 2020-06-25
    相关资源
    最近更新 更多