Wow, this entry is getting a lot of traffic from DZone and other places. If you like this post, be sure to check out New Year’s Benchmarks for PHP framework comparisons, and the Solar Framework for PHP 5. Thanks for visiting!
One of the nice things about using a scripting language is that it automates garbage collection for you. You don’t have to worry about releasing memory when you’re done with your variables. As each variable passes out of scope, PHP frees that memory for you. If you want to, you can free the memory yourself using unset(), but usually you don’t have to.
But there is at least one circumstance in which PHP will not free memory for you when you call unset(). Cf. http://bugs.php.net/bug.php?id=33595.
Problem
If you have two objects in circular reference, such as in a parent-child relationship, calling unset() on the parent object will not free the memory used for the parent reference in the child object. (Nor will the memory be freed when the parent object is garbage-collected.)
Confusing? Here’s a script you can run to see it in action:
<?php
class Foo {
function __construct()
{
$this->bar = new Bar($this);
}
}
class Bar {
function __construct($foo = null)
{
$this->foo = $foo;
}
}
while (true) {
$foo = new Foo();
unset($foo);
echo number_format(memory_get_usage()) . "\\n";
}
?>
Run that and watch your memory usage climb and climb, until memory runs out.
...
33,551,616
33,551,976
33,552,336
33,552,696
PHP Fatal error: Allowed memory size of 33554432 bytes exhausted
(tried to allocate 16 bytes) in memleak.php on line 17
For most PHP developers this behavior is not likely to be a problem. However, if you use a lot of objects in parent-child relationships over a long-running script, you can run out of memory pretty quickly, especially if those objects are relatively large. I discovered this myself while testing some ORM-related pieces for Solar and it took me a couple days to figure it out — hence this blog post. ;-)
Userland Solution
The bugs.php.net link above presents a solution, albeit inelegant and tedious. The “fix” is to call a destructor method before unsetting the object. The destructor method should clear out any internal parent object references, which will free the memory that would otherwise leak.
The “fixed” script looks like this:
<?php
class Foo {
function __construct()
{
$this->bar = new Bar($this);
}
function __destruct()
{
unset($this->bar);
}
}
class Bar {
function __construct($foo = null)
{
$this->foo = $foo;
}
}
while (true) {
$foo = new Foo();
$foo->__destruct();
unset($foo);
echo number_format(memory_get_usage()) . "\\n";
}
?>
Note the new Foo::__destruct() method and the call to $foo->__destruct() before unsetting. Now the script will run forever, showing you the amount of memory it uses (which never gets any bigger, thank goodness).
PHP Internals Solution?
Why does the memory leak occur? I am not wise in the ways of PHP internals, but it has something to do with reference counts. The refcount for the child $foo reference inside $bar does not get decremented when the parent $foo is unset, so PHP thinks the $foo object is still needed and doesn’t release the memory for it … or something like that. I am sure to be displaying my ignorance here, but the general idea is the same: a refcount is not decremented, so some memory never gets released.
I get from the bugs.php.net link above that modifying the garbage-collection process to check for this kind of issue would be a performance-killer, and what little I know about refcounts makes me think this is true.
Instead of changing the garbage-collection process, would it make sense to have unset() do some extra processing on the variable to look for internal objects and unset them too? (Or perhaps to call __destruct() on objects being unset?) Maybe a PHP internals person can comment here or elsewhere on the practicality and/or wisdom of such changes.
UPDATE: Martin Fjordvald notes in the comments that a patch from David Wang for garbage collection does exist and is under consideration. (Did I say “patch”? More like “whole sheet of cloth” — it’s huge. See the CVS checkout instructions at the end of that email.) Problem is that it didn’t garner many votes for inclusion in 5.3. A nice compromise solution might be to have the unset() function call the __destruct() method of objects sent to it; that would seem intuitively appropriate here.
Paul,
I thought I heard that one of the SoC students was working on garbage collection improvements for PHP, any idea if the work he/she has done addresses any of this? I’ve ran into this issue too, it can be a real pain unfortunately.
Pax,
- Stan
Stan:
http://news.php.net/php.internals/30790
So essentially he’s done with the code and is testing it to iron out bugs/improve performance.
That the PHP team isn’t giving it too much attention is an entirely different thing.
http://news.php.net/php.internals/32330
Yes, there is a patch for 5.2, but right now it looks like the patch will not make it into PHP until version 6.
Interesting, I wrote about the same subject about a couple of weeks ago.
http://www.alexatnet.com/node/73
Fortunately, PHP 5.3 may have this issue resolved. See the comments to my article. Thanks!
Paul Jones’ Blog: Memory Leaks With Objects in PHP 5…
…
I can affirm that. The only way I found was to make the object instantiated null instead of unset().
@ Alex@Net: it seems it won’t be resolved in PHP 5.3. See the links in Martin Fjordvald’s comment: “Implement David’s Circular Garbage collection patch” didn’t had many votes. Unfortunately, because this is a *major* issue for object oriented systems.
[...] Jones presents a problem he was having in a new post to his blog – the issue was with memory leaks in objects in a PHP5 [...]
@ rodrigo moraes: thank you for pointing me on it.
[...] wenn das Szenario etwas beschränkt ist) Und ein kurzer Blogeintrag über ein MemoryLeak in PHP Memory Leaks With Objects in PHP 5 __________________ Aktuelles Buch: Oscar Wilde – The Picture of Dorian Gray Letztes Buch: [...]
RE: Having unset call __destruct
Unfortunately, this would completely kill any references to an existing object, for instance:
$a = new MyObject();
$b = $a;
unset($a);
The object should (correctly) live on in $b; calling __destruct when $a is unset would be incorrect. Determining when the instance is no longer being used is the entire problem: the PHP garbage collector cannot resolve recursive references, thus it cannot collect the instance.
Hi Andrew — I see your point. It does look like a modification to the garbage collector is the only way to go. Thank you for the clarification and example!
You could also define a magic __unset method in the class, couldn’t you?
http://www.php.net/manual/en/language.oop5.overloading.php
__unset could destroy the circular reference for you, without having to call __destruct explicitly before unsetting the object.
Hi David — as with __get() and __set(), the magic __unset() method applies to pseudo-properties of the object, not the object itself. Wish it was that easy. :-(
Andrew,
That example you posted works with the php4 mindset. In 4, objects were treated like primatives, and were copied for assignments and function calls. However, in 5, as a developer you are expected to clone objects that are needed but not in your control. Calling __destruct would be proper behavior for a proper OO language, but it turns out that PHP is just too nice to you people. Perhaps they should move to a manual reference count a la Obj-C’s retain/release.
Sin,
That’s not entirely correct either. You are expected to clone objects when you actually want a copy. You can, however, assign two variables to the same instance of the object without cloning — if you couldn’t (or “shouldn’t”) hold multiple references to a single object instance, you’d never run into circular references in the first place.
[...] ir pora straipsnių programavimo tematika. “Memory Leaks With Objects in PHP 5“(šį linkÄ… radau pas LakÅ«nÄ… . AÄiÅ« jam :] ) ir “Managing Hierarchical Data in [...]
My solution:
A base class for all my objects that implements a generic destructor, e.g.:
class BASE {
function __destruct() {
foreach ($this as $index => $value) unset($this->$index);
}
}
This was no problem to implement, as all of my persistent classes use a base class anyway, so I just extended that – if your application is based on a framework of some sort, this is most likely true for you as well.
One important thing to keep in mind, is if your classes do implement their own __destruct() method, that the destructor of the base class is NOT automatically called. Again, for me, this was no problem, as the majority of our classes did not have custom destructors. If any classes do implement a destructor, you must call parent::__destruct() at the end of it.
In batch scripts and daemons, after each iteration, it is still necessary to manually call __destruct() and then unset() on every used object. My solution for that is the following small global function:
function destroy(&$var) {
if (is_object($var)) $var->__destruct();
unset($var);
}
Now, instead of __destruct() and then unset() on every object you’re done with, just call destroy($object) instead.
This also ensures that you get an error message for any objects you try to dispose of, where the __destruct() method is not implemented (or inherited), giving you an easy way to find the places where you need to fix this.
In my case, this approach was used on a batch email system with 100.000+ lines of code. Implementing this solution took approximately one hour, and our script batch-processes thousands of email generations now without leaking any memory at all.
I hope this helps somebody else! :-)
If you look at the bug it’s set to fixed for 5.3
solved on php 5? hahahaha… I am having this problem right now with 5.2!
Rasmus Schultz, thanks for your example, it worked great for me and has really saved my bacon today!
We are using 5.1.6 and experiencing huge memory leaks. We’re not yet able to pinpoint it to PHP, but we believe it is and we know that it is Apache causing these leaks. Thanks for your insight, I have e-mailed this useful information to my team and we will look for any objects that instantiate other objects without the object instantiating the other object also unsetting it. Hopefully this is our problem, but we are having a memory flood rather than a leak and this may not be it.
[...] Paul M. Jones » Blog Archive » Memory Leaks With Objects in PHP 5 [...]
[...] this point, a former colleague suggested that this might be caused by a known PHP 5.2 bug which leaves processes hanging when done with them. I thought this might be a good thing to suggest [...]
If you use classes from complicated structure, you cannot solve this in this way (without expensive refactoring)..
In my project, i tried other way – redirecting to the same script. Simply version below:
public function indexAction()
{
if (!isset($_SESSION['counter'])) {
$_SESSION['counter'] = 0;
$_SESSION['limit'] = 4;
$_SESSION['memory_start'] = array();
$_SESSION['memory_stop'] = array();
}
$counter = $_SESSION['counter'];
$memory_start = $_SESSION['memory_start'];
$memory_stop = $_SESSION['memory_stop'];
if ($_SESSION['counter'] < $_SESSION['limit']) {
$memory_start[] = memory_get_usage();
$sth = array();
$rnd = rand(10, 10000);
for($i = 0; $i++ < $rnd;) {
$sth[] = $i;
}
$counter++;
$memory_stop[] = memory_get_usage();
$_SESSION['counter'] = $counter;
$_SESSION['memory_start'] = $memory_start;
$_SESSION['memory_stop'] = $memory_stop;
header(“Location: /test”,true,302);
die();
}
echo “”;
print_r ($_SESSION['memory_start']);
print_r ($_SESSION['memory_stop']);
echo “”;
unset($_SESSION['counter']);
die();
}
It is maybe a stupid question. Is implementing factory design pattern can be use to manage this problem?
in case you are debuging memory leak there is also problem with include() in some PHP versions
see http://bugs.php.net/bug.php?id=47038
[...] anderen Ursachen auszuschließen. Der Vollständigkeit halber kannst Du ja mal versuchen, die hier vorgeschlagene Userland Solution in Dein Projekt zu integrieren, aber wie gesagt… Gruß [...]
[...] management is still PHP’s big issue. Especially using objects with circular references creates memory leaks. While rendering a website this is not a problem because the number of objects is rather small. [...]
[...] http://derickrethans.nl/circular_references.php http://paul-m-jones.com/?p=262 [...]
[...] http://derickrethans.nl/circular_references.php http://paul-m-jones.com/?p=262 [...]
this issue is solved in php 5.3
ehm, no i don’t think it’s solved in php 5.3 ! i wish it was, but it’s exactly the same! scripts are still grabbing gigabytes of RAM because of this.
[...] generated code it’s very likely we have circular references like this. Paul M. Jones also writes about the same issue and illustrates a way to deal with it in your [...]
[...] Memory Leaks With Objects in PHP 5 [...]
[...] Attention aux références cycliques entre objets. Celles-ci sont causes de fuites mémoire importantes. Je ne vais pas prendre la peine de réexpliquer le problème car il existe déjà de très bon articles sur le sujet tel que celui de Paul M. Jones [...]
Working perfectly in PHP 5.3.2 now. Seems to be fixed. At least Garbage Collection start every now and then and cleans memory a little.
this is exactly happening with me. i am using PEARs to extract large number of xml files in a loop. memory consumption is always increasing on each cycle :(
[...] much organized garbage collection ( this has to be implemented using our own logic ) to see more read this Possibly related posts: (automatically generated)“Fieldwork” ch 7 responsePHP Interview [...]
http://php.net/manual/en/features.gc.php
^—May be this link will help about the internal working of PHP.
btw nice article and nice suggestion of using __destructor i was searching on PHP memory leaks and found this page :)
–Abhishek
Interesting reading…
On the same topic: http://wildness.espix.org/index.php?post/2011/05/09/PHP5%3A-Memory-leak-with-object-arrays
I’m using someone else’s class and the implementation requires new object to be created in a loop. Will this cause memory leak? The class doesn’t seem to have function to release or empty the object so moving the object creation outside of the loop will result adding more and more content into the object. Any thoughts?
What do you use to monitor PHP memory usage?
Hello, I have a question.
in this code
1.Does “__destruct” work automatically after perform “new name(“Dan”)”?/in wampserver it works automatically!!
2.I saw it’s better to write $foo->__destruct(); so what? )-:
myname =$data;
}
function __destruct{
unset($this->myname);
}
}
$v = new name(“Dan”);
unset($v);
?>
Hello, I have a question.
in this code
1.Does “__destruct” work automatically after perform “new name”?/in wampserver it works automatically!!
2.I saw it’s better to write $foo->__destruct(); so what? )-:
myname =$data;
}
function __destruct{
unset($this->myname);
}
}
$v = new name(“Dan”);
unset($v);
?>
Thanks for this interesting article.
It seems that this problem no longer exists with PHP 5.3.x – at least the example mentioned above works fine with PHP 5.3.3 and GC turned on. Maybe you should mention this in your post.
Bye
Christian
[...] acceptable, and it seems that the old memory leaks have been fixed (read more over here, here and here [...]
[...] to figure out what was going wrong (using Xdebug's function traces, PHP's garbage collection, the unset trick of Paul M. Jones, etc…), I turned to a simple but effective manner to inspect the (problem) [...]