36

I've had some issues lately with PHP memory limits lately:

Out of memory (allocated 22544384) (tried to allocate 232 bytes)

These are quite the nuisance to debug since I'm not left with a lot of info about what caused the issue.

Adding a shutdown function has helped

register_shutdown_function('shutdown');

then, using error_get_last(); I can obtain information about the last error, in this case, the "Out of memory" fatal error, such as the line number, and the php file name.

This is nice and all, but my php program is heavily object oriented. An error deep in the stack doesn't tell me much about the control structure or the execution stack at the moment of the error. I've tried debug_backtrace(), but that just shows me the stack during shutdown, not the stack at the time of the error.

I know I can just raise the memory limit using ini_set or modifying php.ini, but that doesn't get me any closer to actually figuring out what is consuming so much memory or what my execution flow looks like during the error.

Anyone have a good methodology for debugging memory errors in advanced Object Oriented PHP programs?

Kevin Owocki
  • 1,368
  • 2
  • 11
  • 9
  • 232 bytes and you're getting a out of memory issue? o.O – Shaz May 24 '11 at 17:10
  • What you storing in your objects that'd require 20+ megs of ram? – Marc B May 24 '11 at 17:15
  • 10
    @Shaz, PHP dies because it cannot allocate 232 bytes on top of what it already allocated so far (which is the memory limit). – rid May 24 '11 at 17:17
  • 4
    See [PHP memory profiling](http://stackoverflow.com/questions/880458/php-memory-profiling). – rid May 24 '11 at 17:18
  • 1
    @Marc, that's what I'm trying to find out :P – Kevin Owocki May 24 '11 at 17:19
  • @rdineiu thank you - xdebug seems like a great solution for when i've already got some leads on the issue. the problem i am having is that i know nothing about the issues other than the line number and file name that the error occured on. – Kevin Owocki May 24 '11 at 17:21
  • Var_dumps + exits. Lots of var_dumps & exits.... Or preferably, xdebug! – Aaria Carter-Weir May 24 '11 at 17:21
  • @Kevin: the obvious places to look: repeated string building, file manipulation (particularly loading large images via GD and the like), building large Excel files, etc...), array construction. Everything in PHP takes up more memory than the raw data suggests it would, due to PHP engine overhead. – Marc B May 24 '11 at 17:23
  • Certain very well known libraries (such as Doctrine or the Symfony framework) create incredible amounts of objects. Especially Doctrine. I had to rewrite an entire part of an application once to avoid Doctrine, because it was unusably slow. – rid May 24 '11 at 17:27

6 Answers6

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 ... */

I use this to print out a list of currently assigned variables just before a problem section of my code, along with a (very) rough estimate of the size of the variable. I go back and unset anything that isn't needed at and beyond the point of interest.

It's useful when installing an extension isn't an option.

You could modify the above code to use memory_get_usage in a way that will give you a different estimate of the memory in a variable, not sure whether it'd be better or worse.

Nick Pickering
  • 3,095
  • 3
  • 29
  • 50
8

Memprof is a php extension that helps finding those memory-eaters snippets, specially in object-oriented codes.

This adapted tutorial is quite useful.

Note: I unsuccessfully tried to compile this extension for windows. If you try so, be sure your php is not thread safe. To avoid some headaches I suggest you to use it under *nix environments.

Another interesting link was a slideshare describing how php handles memory. It gives you some clues about your script's memory usage.

5

I wonder is perhaps your thinking regards methodology is flawed here.

The basic answer to your question - how do I find out where this error is occurring? - has already been answered; you know what's causing that.

However, this is one of those cases where the triggering error isn't really the problem - certainly, that 232 byte object isn't your problem at all. It is the 20+Megs that was allocated before it.

There have been some ideas posted which can help you track that down; you really need to look "higher level" here, at the application architecture, and not just at individual functions.

It may be that your application requires more memory to do what it does, with the user load you have. Or it may be that there are some real memory hogs that are unnecessary - but you have to know what is necessary or not to answer that question.

That basically means going line-by-line, object-by-object, profiling as needed, until you find what you seek; big memory users. Note that there might not be one or two big items... if only it were so easy! Once you find the memory-hogs, you then have to figure out if they can be optimized. If not, then you need more memory.

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
  • Certainly, if you find out that your application is using 20 MB of memory and you don't know why, it's time to take a step back and look at the architecture again. Take a few days to look it over, see call graphs, see memory profiles, and don't be afraid to refactor! – rid May 24 '11 at 17:34
  • 3
    It isn't necessarily an architecture problem. You can get out of memory errors with accidental infinite recursion or endless loops, which will eat **all** memory no matter how much you allocate. – Nick Apr 05 '17 at 17:59
2

Website "IF !1 0" provides a simple to use MemoryUsageInformation class. It is very useful for debugging memory leaks.

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

}
tixastronauta
  • 404
  • 4
  • 17
2

Check the documentation of the function memory_get_usage() to view the memory usage in run time.

fvox
  • 1,077
  • 6
  • 8
  • 3
    Thanks. I'm aware of memory_get_usage(), but it seems sort of sloppy and inefficient to drop them haphazardly throughout my codebase hoping that I catch memory usage at a peak. I'm interested in methodologies that use this function though. – Kevin Owocki May 24 '11 at 17:17
0

Use xdebug to profile memory usage.

brian_d
  • 11,190
  • 5
  • 47
  • 72
  • 2
    thank you - xdebug seems like a great solution for when i've already got some leads on the issue. the problem i am having is that the application is huge and i know nothing about the issues other than the line number and file name that the error occured on. – Kevin Owocki May 24 '11 at 17:22