php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #33595 recursive references leak memory
Submitted: 2005-07-06 23:44 UTC Modified: 2008-01-29 11:35 UTC
Votes:91
Avg. Score:4.7 ± 0.7
Reproduced:73 of 78 (93.6%)
Same Version:16 (21.9%)
Same OS:37 (50.7%)
From: rodricg at sellingsource dot com Assigned: dmitry (profile)
Status: Closed Package: Class/Object related
PHP Version: 6CVS-2005-08-02 OS: *
Private report: No CVE-ID: None
 [2005-07-06 23:44 UTC] rodricg at sellingsource dot com
Description:
------------
Objects with recursive references leak memory. 
PHP 5.1.0b2 exhibits the same behavior. 

Reproduce code:
---------------
<?php
class A {
    function __construct () {
        $this->b = new B($this);
    }
}

class B {
    function __construct ($parent = NULL) {
        $this->parent = $parent;
    }
}

for ($i = 0 ; $i < 1000000 ; $i++) {
    $a = new A();
}
?>

Expected result:
----------------
Memory usage should remain constant. 

Actual result:
--------------
Memory usage quickly increases to consume several hundred 
megabytes. 

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2005-07-07 03:49 UTC] rodricg at sellingsource dot com
Distinctly different than bug #33487 this only occurs when the object contains a recursive reference.  Add 
echo number_format(memory_get_usage());
to the end of the script and you get 346,360,656.  Then comment out $this->parent = $parent and run again to get 53,832.  I suspect the internal reference is not properly invalidated upon the destruction of class A.  This would keep the ref count > 0 and foil garbage collection.  Unless I am missing something....
 [2005-08-01 11:00 UTC] dmitry@php.net
Yes. This is different bug. However it is not very critical (as bug #33487 too), because all memory is freed on request shutdown.
I don't know how to implement support for circular data structures freeing without significant performance lose.
 [2005-12-05 08:40 UTC] marek at lewczuk dot com
The bug still exists in 5.1.1. If this cannot be solved in near future then I think it should be noted somewhere in the manual.
 [2006-01-17 17:56 UTC] letssurf at gmail dot com
I agree this is a very annoying bug for large scale projects and should be fixed or noted in the manual.

What is the best work around for this bug?
 [2006-01-17 19:08 UTC] rodricg at sellingsource dot com
You can avoid the memory leak by manually calling the destructors and unsetting the recursive ref:

<?php
class A {
    function __construct () {
        $this->b = new B($this);
    }
    function __destruct () {
        $this->b->__destruct();
    }
}

class B {
    function __construct ($parent = NULL) {
        $this->parent = $parent;
    }
    function __destruct () {
        unset($this->parent);
    }
}

for ($i = 0 ; $i < 1000000 ; $i++) {
    $a = new A();
    $a->__destruct();
}

echo number_format(memory_get_usage());
?>
 [2006-01-19 10:31 UTC] letssurf at gmail dot com
Thank you for your solution. It's the same path we had taken here. We had created a kill() method to do the same job. Shame it hasn't been fixed.

Thank you again :)
 [2006-02-22 15:12 UTC] K dot Londenberg at librics dot de
The problem with circular references is a known weakness of reference counting schemes. Python uses a workaround (cycle detector). 

Excerpt from http://wingware.com/psupport/python-manual/2.4/ext/refcounts.html:

"
While Python uses the traditional reference counting implementation, it also offers a cycle detector that works to detect reference cycles. This allows applications to not worry about creating direct or indirect circular references; these are the weakness of garbage collection implemented using only reference counting. Reference cycles consist of objects which contain (possibly indirect) references to themselves, so that each object in the cycle has a reference count which is non-zero. Typical reference counting implementations are not able to reclaim the memory belonging to any objects in a reference cycle, or referenced from the objects in the cycle, even though there are no further references to the cycle itself. "

Maybe this would also be a viable Strategy for PHP..
 [2006-04-13 06:24 UTC] seufert at gmail dot com
I have been experiencing this problem. Unfortunately i have an application which relies heavily on a class->driver arrangement where both the class and the driver class need a reference to each other. I was wondering if we could have a way of getting an uncounted reference to the object to pass to the child? So the child would have a reference to the parent, but it would not be counted, and therefore when all external references to the parent are removed/unset, the parent will GC, and then the child will be freed as usual.

Is this a workable solution? Even just a reference count decrement would work. Not a perfect solution, but it would solve calling descructors all the time.
 [2006-05-04 14:08 UTC] frode at coretrek dot com
I worked around this exact problem by using a proxy class with a destructor that explicitly breaks the cycle; I got the idea from a perl.com[1] article describing how perl suffers from the same problem. It's also interesting to note that perl has chosen a different (less elegant) solution to the problem than python, by introducing weak references.

[1] http://www.perl.com/pub/a/2002/08/07/proxyobject.html
 [2006-08-15 12:02 UTC] ruslan dot kyrychuk at gmail dot com
Maybe solutution can be to call destructor every time when new object is assigned to old reference?
Example:
<?php
class A {
    function __construct () {
        $this->b = new B($this);
    }
    function __destruct () {
        $this->b->__destruct();
    }
}

class B {
    function __construct ($parent = NULL) {
        $this->parent = $parent;
    }
    function __destruct () {
        unset($this->parent);
    }
}

for ($i = 0 ; $i < 1000000 ; $i++) {
    $a = new A();
    $a->__destruct();
}

echo number_format(memory_get_usage());
?>

$a->__destruct(); can be called automatically because new reference is created. Then writing correct destructors will solve this issue.

Or maybe I missing something?
 [2006-11-01 09:07 UTC] taneli at crasman dot fi
Please address this bug, it's very short-sighted to rely on the fact that memory is freed on shutdown (especially when your either your box starts trashing or memory_limit kicks in and fails your request because of a bug in the interpreter).
 [2006-11-02 22:28 UTC] judas dot iscariote at gmail dot com
taneli at crasman dot fi : 

 - This is a well known "gotcha"  see [1] 
 - for the gory details, please see [2], this is not "that easy" to fix.


1. http://en.wikipedia.org/wiki/Reference_counting#Advantages_and_disadvantages

2. ftp://ftp.cs.utexas.edu/pub/garbage/bigsurv.ps
 [2007-01-24 13:53 UTC] taneli at crasman dot fi
It shouldn't be a known "got cha". 

I just wrote a backgrounded daemon using PHP and I had to instate a checker process just to restart the process any time it dies (because it leaks memory about 30 megs per "daemon loop", because of references).
 [2007-05-25 19:50 UTC] wckits at rit dot edu
Any suggestion to call the destructor explicitly is misguided at best and dangerous at worst. You may have another reference to that object somewhere, and it will end up being a reference to a destructed object.
 [2007-08-03 09:37 UTC] zwacks10 at yahoo dot com
This Forum is help full thanks to you guys.
 [2007-08-04 00:40 UTC] zwacks10 at yahoo dot com
<?php
class A {
    function __construct () {
        $this->b = new B($this);
    }
}

class B {
    function __construct ($parent = NULL) {
        $this->parent = $parent;
    }
}

for ($i = 0 ; $i < 1000000 ; $i++) {
    $a = new A();
    unset($a);
}

echo number_format(memory_get_usage());
?>


Try this code. you will also avoid the memory leak...

This forum really helps me to solve this problem. You gave me an idea to avoid this bugs. i really appreciate it. Thanks a lot guys.
 [2007-11-22 14:48 UTC] shez at starfangled dot net
Hi,

Does anybody have an easy way to trace _what_ objects have been 
leaked due to circular references?  I've had couple of cases in 
large code bases that I've had to trace by the old binary search 
algorithm (you know the one, remove half of the code, test, etc ;)

Cheers!
Shez
 [2008-01-29 11:35 UTC] dmitry@php.net
Fixed with GC patch in CVS HEAD and PHP_5_3.
 [2010-09-28 10:45 UTC] a at a dot com
Fix in PHP 5.3.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Tue Mar 19 02:01:28 2024 UTC