如何调试PHP的“内存不足”问题?

36

最近我遇到了一些PHP内存限制的问题:

内存不足(已分配22544384)(尝试分配232字节)

这些问题很让人头疼,因为我没有太多关于��致问题的信息。

添加一个关闭函数已经有所帮助。

register_shutdown_function('shutdown');

然后,使用error_get_last()函数,我可以获取有关最后一个错误的信息,例如“Out of memory”致命错误的行号和PHP文件名。

这很好,但我的PHP程序是面向对象的。堆栈中的错误并不能告诉我关于控制结构或错误发生时的执行堆栈的太多信息。我尝试了debug_backtrace(),但它只显示了关闭期间的堆栈,而不是错误发生时的堆栈。

我知道我可以使用ini_set或修改php.ini来增加内存限制,但这并不能让我更接近实际找出是什么消耗了如此多的内存或者我的执行流程在错误期间是什么样子的。

是否有人有一种良好的方法来调试高级面向对象的PHP程序中的内存错误呢?


232字节就出现内存不足问题了?o.O - Shaz
10
@Shaz,PHP死亡是因为它无法在已经分配的内存上再分配232字节(即内存限制)。 - rid
4
请参阅PHP内存分析 - rid
1
@Marc,这就是我试图找出来的:P - Kevin Owocki
某些非常著名的库(例如Doctrine或Symfony框架)会创建大量的对象,尤其是Doctrine。我曾经不得不重写应用程序的整个部分来避免使用Doctrine,因为它的速度无法接受。 - rid
显示剩余4条评论
6个回答

11
echo '<pre>';
$vars = get_defined_vars();
foreach($vars as $name=>$var)
{
    echo '<strong>' . $name . '</strong>: ' . strlen(serialize($var)) . '<br />';
}
exit();

/* ... Code that triggers memory error ... */
我会在代码中出现问题的部分之前,打印出当前分配变量的列表,以及变量大小的(非常)粗略估计。我会回到需要操作的位置并unset掉不需要的变量及其后面的变量。
如果无法安装扩展程序,则此方法非常有用。
您可以修改上述代码,以使用memory_get_usage,以获得变量内存使用的不同估计值,但不确定它是否更好或更差。

8

Memprof是一款PHP扩展,可以帮助查找那些占用内存过多的代码片段,特别是在面向对象的代码中。

这个经过改编的教程非常有用。

注意:我尝试在Windows上编译此扩展失败。如果您想尝试,请确保您的PHP不是线程安全的。为了避免一些麻烦,建议您在*nix环境下使用它。

另一个有趣的链接是slideshare,其中描述了PHP如何处理内存。它可以给您一些关于脚本内存使用情况的线索。


5
我想也许您在方法论方面的思考存在缺陷。
对于您的问题 - 如何找出错误发生的位置?- 已经有了基本答案; 您知道是什么导致了这个错误。
然而,在这种情况下,触发错误并不是真正的问题 - 当然,那个232字节的对象根本不是您的问题。重点是分配给它之前的20多兆字节。
已经有一些想法可以帮助您跟踪查找;您确实需要从“更高层次”来看待这个问题,看看应用程序架构,而不仅仅是单个函数。
也许您的应用程序需要更多的内存才能完成其任务,同时满足用户负载。或者可能有一些真正的内存占用量过大的东西是不必要的 - 但是您必须知道什么是必要的或不必要的,以回答这个问题。
这基本上意味着逐行逐个对象地进行分析,并根据需要进行分析,直到找到所需的内容为止;大内存用户。请注意,可能没有一个或两个大项目...如果只有这样就简单了!一旦找到内存占用量大的项目,然后您需要弄清楚它们是否可以被优化。如果不能,那么您需要更多的内存。

当你发现你的应用程序使用了20 MB的内存,而你不知道为什么时,一定要退后一步,重新审视架构。花几天时间检查它,查看调用图、内存配置文件,不要害怕重构! - rid
3
这并不一定是一个架构问题。如果出现意外的无限递归或无限循环,就算你分配了多少内存,也会导致内存溢出错误。 - Nick

2

3
谢谢。我知道memory_get_usage()函数,但是在我的代码库中随意地添加它们并希望在峰值时捕获内存使用似乎有些草率和低效。尽管如此,我仍然对使用这个函数的方法很感兴趣。 - Kevin Owocki

2

网站“IF !1 0”提供了一个易于使用的MemoryUsageInformation类。它对于调试内存泄漏非常有用。

<?php

class MemoryUsageInformation
{

    private $real_usage;
    private $statistics = array();

    // Memory Usage Information constructor
    public function __construct($real_usage = false)
    {
        $this->real_usage = $real_usage;
    }

    // Returns current memory usage with or without styling
    public function getCurrentMemoryUsage($with_style = true)
    {
        $mem = memory_get_usage($this->real_usage);
        return ($with_style) ? $this->byteFormat($mem) : $mem;
    }

    // Returns peak of memory usage
    public function getPeakMemoryUsage($with_style = true)
    {
        $mem = memory_get_peak_usage($this->real_usage);
        return ($with_style) ? $this->byteFormat($mem) : $mem;
    }

    // Set memory usage with info
    public function setMemoryUsage($info = '')
    {
        $this->statistics[] = array('time' => time(),
            'info' => $info,
            'memory_usage' => $this->getCurrentMemoryUsage());
    }

    // Print all memory usage info and memory limit and 
    public function printMemoryUsageInformation()
    {
        foreach ($this->statistics as $satistic)
        {
            echo "Time: " . $satistic['time'] .
            " | Memory Usage: " . $satistic['memory_usage'] .
            " | Info: " . $satistic['info'];
            echo "\n";
        }
        echo "\n\n";
        echo "Peak of memory usage: " . $this->getPeakMemoryUsage();
        echo "\n\n";
    }

    // Set start with default info or some custom info
    public function setStart($info = 'Initial Memory Usage')
    {
        $this->setMemoryUsage($info);
    }

    // Set end with default info or some custom info
    public function setEnd($info = 'Memory Usage at the End')
    {
        $this->setMemoryUsage($info);
    }

    // Byte formatting
    private function byteFormat($bytes, $unit = "", $decimals = 2)
    {
        $units = array('B' => 0, 'KB' => 1, 'MB' => 2, 'GB' => 3, 'TB' => 4,
            'PB' => 5, 'EB' => 6, 'ZB' => 7, 'YB' => 8);

        $value = 0;
        if ($bytes > 0)
        {
            // Generate automatic prefix by bytes 
            // If wrong prefix given
            if (!array_key_exists($unit, $units))
            {
                $pow = floor(log($bytes) / log(1024));
                $unit = array_search($pow, $units);
            }

            // Calculate byte value by prefix
            $value = ($bytes / pow(1024, floor($units[$unit])));
        }

        // If decimals is not numeric or decimals is less than 0 
        // then set default value
        if (!is_numeric($decimals) || $decimals < 0)
        {
            $decimals = 2;
        }

        // Format output
        return sprintf('%.' . $decimals . 'f ' . $unit, $value);
    }

}

0
使用 xdebug 来分析内存使用情况。

2
谢谢 - Xdebug似乎是一个很好的解决方案,当我已经有一些线索时。我遇到的问题是应用程序非常庞大,除了错误发生的行号和文件名之外,我对问题一无所知。 - Kevin Owocki

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