Php – Why does this simple php script leak memory

memoryPHP

In hopes of trying to avoid future memory leaks in php programs (drupal modules, etc.) I've been messing around with simple php scripts that leak memory.

Could a php expert help me find what about this script causes the memory usage to continually climb?

Try running it yourself, changing various parameters. The results are interesting. Here it is:

<?php

function memstat() {
  print "current memory usage: ". memory_get_usage() . "\n";
}

function waste_lots_of_memory($iters) {
  $i = 0;
  $object = new StdClass;
  for (;$i < $iters; $i++) {
    $object->{"member_" . $i} = array("blah blah blha" => 12345);
    $object->{"membersonly_" . $i} = new StdClass;
    $object->{"onlymember"} = array("blah blah blha" => 12345);
  }
  unset($object);
}

function waste_a_little_less_memory($iters) {
  $i = 0;
  $object = new StdClass;
  for (;$i < $iters; $i++) {

    $object->{"member_" . $i} = array("blah blah blha" => 12345);
    $object->{"membersonly_" . $i} = new StdClass;
    $object->{"onlymember"} = array("blah blah blha" => 12345);

    unset($object->{"membersonly_". $i});
    unset($object->{"member_" . $i});
    unset($object->{"onlymember"});

  }
  unset($object);
}

memstat();

waste_a_little_less_memory(1000000);

memstat();

waste_lots_of_memory(10000);

memstat();

For me, the output is:

current memory usage: 73308
current memory usage: 74996
current memory usage: 506676

[edited to unset more object members]

Best Answer

unset() doesn't free the memory used by a variable. The memory is freed when the "garbage collector" (in quotes since PHP didn't have a real garbage collector before version 5.3.0, just a memory free routine which worked mostly on primitives) sees fit.

Also, technically, you shouldn't need to call unset() since the $object variable is limited to the scope of your function.

Here is a script to demonstrate the difference. I modified your memstat() function to show the memory difference since the last call.

<?php
function memdiff() {
    static $int = null;

    $current = memory_get_usage();

    if ($int === null) {
        $int = $current;
    } else {
        print ($current - $int) . "\n";
        $int = $current;
    }
}

function object_no_unset($iters) {
    $i = 0;
    $object = new StdClass;

    for (;$i < $iters; $i++) {
        $object->{"member_" . $i}= array("blah blah blha" => 12345);
        $object->{"membersonly_" . $i}= new StdClass;
        $object->{"onlymember"}= array("blah blah blha" => 12345);
    }
}

function object_parent_unset($iters) {
    $i = 0;
    $object = new StdClass;

    for (;$i < $iters; $i++) {
        $object->{"member_" . $i}= array("blah blah blha" => 12345);
        $object->{"membersonly_" . $i}= new StdClass;
        $object->{"onlymember"}= array("blah blah blha" => 12345);
    }

    unset ($object);
}

function object_item_unset($iters) {
    $i = 0;
    $object = new StdClass;

    for (;$i < $iters; $i++) {

        $object->{"member_" . $i}= array("blah blah blha" => 12345);
        $object->{"membersonly_" . $i}= new StdClass;
        $object->{"onlymember"}= array("blah blah blha" => 12345);

        unset ($object->{"membersonly_" . $i});
        unset ($object->{"member_" . $i});
        unset ($object->{"onlymember"});
    }
    unset ($object);
}

function array_no_unset($iters) {
    $i = 0;
    $object = array();

    for (;$i < $iters; $i++) {
        $object["member_" . $i] = array("blah blah blha" => 12345);
        $object["membersonly_" . $i] = new StdClass;
        $object["onlymember"] = array("blah blah blha" => 12345);
    }
}

function array_parent_unset($iters) {
    $i = 0;
    $object = array();

    for (;$i < $iters; $i++) {
        $object["member_" . $i] = array("blah blah blha" => 12345);
        $object["membersonly_" . $i] = new StdClass;
        $object["onlymember"] = array("blah blah blha" => 12345);
    }
    unset ($object);
}

function array_item_unset($iters) {
    $i = 0;
    $object = array();

    for (;$i < $iters; $i++) {
        $object["member_" . $i] = array("blah blah blha" => 12345);
        $object["membersonly_" . $i] = new StdClass;
        $object["onlymember"] = array("blah blah blha" => 12345);

        unset ($object["membersonly_" . $i]);
        unset ($object["member_" . $i]);
        unset ($object["onlymember"]);
    }
    unset ($object);
}

$iterations = 100000;

memdiff(); // Get initial memory usage

object_item_unset ($iterations);
memdiff();

object_parent_unset ($iterations);
memdiff();

object_no_unset ($iterations);
memdiff();

array_item_unset ($iterations);
memdiff();

array_parent_unset ($iterations);
memdiff();

array_no_unset ($iterations);
memdiff();
?>

If you are using objects, make sure the classes implements __unset() in order to allow unset() to properly clear resources. Try to avoid as much as possible the use of variable structure classes such as stdClass or assigning values to members which are not located in your class template as memory assigned to those are usually not cleared properly.

PHP 5.3.0 and up has a better garbage collector but it is disabled by default. To enable it, you must call gc_enable() once.