Edit report at http://bugs.php.net/bug.php?id=48781&edit=1
ID: 48781
Updated by: [email protected]
Reported by: nate at frickenate dot com
Summary: Cyclical garbage collector memory leak
Status: Assigned
Type: Bug
Package: Scripting Engine problem
Operating System: Debian 5.0 kernel 2.6.24-23-xen
PHP Version: 5.3.0
Assigned To: dmitry
New Comment:
I am unable to duplicate this behaviour using 5.3.2. The code given
below consistently uses 0.65 MB even up to 10 million iterations - I
pasted the output of up to 6 million, with the extended GC reporting
turned on:
<?php
class User {
protected $profile;
public function __construct () {
$this->profile = new UserProfile($this);
}
public function dispose() {
$this->profile->dispose();
$this->profile = null;
}
}
class UserProfile {
private $user;
public function __construct ($user) {
$this->user = $user;
}
public function dispose() {
$this->user = null;
}
}
for ($userid = 1; ; $userid++) {
if(isset($user)) {
$user->dispose();
}
$user = new User;
if ($userid % 100000 == 0)
printf("memory usage after %s users: %s MB.\n",
number_format($userid), number_format(memory_get_usage() / 1024 / 1024,
2));
continue;
}
?>
memory usage after 100,000 users: 0.65 MB.
memory usage after 200,000 users: 0.65 MB.
memory usage after 300,000 users: 0.65 MB.
[snip]
memory usage after 5,700,000 users: 0.65 MB.
memory usage after 5,800,000 users: 0.65 MB.
memory usage after 5,900,000 users: 0.65 MB.
GC Statistics
-------------
Runs: 0
Collected: 0
Root buffer length: 0
Root buffer peak: 9
Possible Remove from Marked
Root Buffered buffer grey
-------- -------- ----------- ------
ZVAL 16 7 7 0
ZOBJ 77999984 11999998 11999998 0
Previous Comments:
------------------------------------------------------------------------
[2010-01-05 19:31:01] nightstorm at tlen dot pl
I confirm, there is a true memory leak. Consider the following script,
where the references are unmounted manually with an extra dispose()
method:
~~~~
<?php
/*
How to use:
a) run test 1 by running code as-is
b) run test 2 by commenting out test 1, uncomment test 2
c) run test 3 by commenting out test 1 & 2, uncomment test 3
*/
class User {
protected
$profile;
public function __construct () {
$this->profile = new UserProfile($this);
}
public function dispose()
{
$this->profile->dispose();
$this->profile = null;
} // end dispose();
}
class UserProfile {
private
$user;
public function __construct ($user) {
$this->user = $user;
}
public function dispose()
{
$this->user = null;
} // end dispose();
}
for ($userid = 1; ; $userid++) {
if(isset($user))
{
$user->dispose();
}
$user = new User;
if ($userid % 100000 == 0)
printf("memory usage after %s users: %s MB.\n",
number_format($userid), number_format(memory_get_usage() / 1024 / 1024,
2));
continue;
}
~~~~~
In this case the result is still the same (PHP 5.3.1):
~~~~
memory usage after 100,000 users: 0.61 MB.
memory usage after 200,000 users: 0.61 MB.
memory usage after 300,000 users: 0.61 MB.
memory usage after 400,000 users: 0.61 MB.
memory usage after 500,000 users: 0.61 MB.
memory usage after 600,000 users: 0.61 MB.
memory usage after 700,000 users: 0.61 MB.
memory usage after 800,000 users: 0.61 MB.
memory usage after 900,000 users: 0.61 MB.
memory usage after 1,000,000 users: 0.61 MB.
memory usage after 1,100,000 users: 0.61 MB.
memory usage after 1,200,000 users: 0.61 MB.
~~~~
If we enable the garbage collector and remove the dispose() method, the
used memory level begins to increase, and if we call gc_collect_cycles()
after creating a new object, the used memory increases even much faster.
On my PC, it is not able to perform display even a single control
message after 100000 iterations. It stopped after approx. 33000 users:
~~~~
memory usage after 30,000 users: 25.37 MB.
memory usage after 31,000 users: 26.07 MB.
memory usage after 32,000 users: 26.76 MB.
memory usage after 33,000 users: 31.46 MB.
Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to
allocate 89 bytes) in /home/me/test/memleak.php on line 17
~~~~
------------------------------------------------------------------------
[2009-12-15 01:00:00] php-bugs at lists dot php dot net
No feedback was provided for this bug for over a week, so it is
being suspended automatically. If you are able to provide the
information that was originally requested, please do so and change
the status of the bug back to "Open".
------------------------------------------------------------------------
[2009-12-08 15:23:16] nate at frickenate dot com
No change, same problem. Considering this bug hasn't even been looked at
or assigned, no surprise there.
------------------------------------------------------------------------
[2009-12-07 09:12:35] [email protected]
Please try using this snapshot:
http://snaps.php.net/php5.3-latest.tar.gz
For Windows:
http://windows.php.net/snapshots/
------------------------------------------------------------------------
[2009-07-02 18:35:42] nate at frickenate dot com
Description:
------------
The new cyclical garbage collector isn't collecting everything it should
be - somewhere there is a memory leak. The issue I am bringing up is not
about delayed collection - this is a true memory leak with memory that
is never reclaimed.
When a variable (in this example, $user) contains an object with a
cyclical reference, there is a small amount of memory that is leaked/not
reclaimed when you set $user to another object (of any class). If you
set $user to an int/string/boolean/null before setting $user to another
object, all memory is collected properly.
I came to the conclusion that this must be a problem with the garbage
collector, since if you replace the "$this->user = $user;" with
"$this->user = new StdClass;" (thus preventing the creation of a
cyclical reference), the leak goes away.
So basically right now:
<?php
while ($user = get_next_user()) {
// do stuff with $user
// php bug: have to unset before next iteration
// to prevent memory leak caused by buggy gc
unset($user);
}
?>
Reproduce code:
---------------
<?php
/*
How to use:
a) run test 1 by running code as-is
b) run test 2 by commenting out test 1, uncomment test 2
c) run test 3 by commenting out test 1 & 2, uncomment test 3
*/
class User {
protected
$profile;
public function __construct () {
$this->profile = new UserProfile($this);
}
}
class UserProfile {
private
$user;
public function __construct ($user) {
$this->user = $user;
}
}
for ($userid = 1; ; $userid++) {
$user = new User;
if ($userid % 100000 == 0)
printf("memory usage after %s users: %s MB.\n",
number_format($userid), number_format(memory_get_usage() / 1024 / 1024,
2));
// TEST 1 - do nothing before we set the
// new User on next iteration of the loop).
// RESULT: leaks memory (see "Actual result" section)
continue;
// TEST 2 - set $user to an empty object, before
// we set the new User on next iteration
// RESULT: leaks memory (see "Actual result" section)
//$user = new StdClass; continue;
// TEST 3 - set $user to anything other than an object,
// before we set the new User on next iteration.
// RESULT: does NOT leak memory (see "Expected result" section)
//$user = 'not an object'; continue;
}
?>
Expected result:
----------------
memory usage after 100,000 users: 1.54 MB.
memory usage after 200,000 users: 1.54 MB.
memory usage after 300,000 users: 1.54 MB.
memory usage after 400,000 users: 1.54 MB.
memory usage after 500,000 users: 1.54 MB.
memory usage after 600,000 users: 1.54 MB.
memory usage after 700,000 users: 1.54 MB.
memory usage after 800,000 users: 1.54 MB.
memory usage after 900,000 users: 1.54 MB.
memory usage after 1,000,000 users: 1.54 MB.
[snip]
memory usage after 99,000,000 users: 1.54 MB.
memory usage after 99,100,000 users: 1.54 MB.
memory usage after 99,200,000 users: 1.54 MB.
memory usage after 99,300,000 users: 1.54 MB.
memory usage after 99,400,000 users: 1.54 MB.
memory usage after 99,500,000 users: 1.54 MB.
memory usage after 99,600,000 users: 1.54 MB.
memory usage after 99,700,000 users: 1.54 MB.
memory usage after 99,800,000 users: 1.54 MB.
memory usage after 99,900,000 users: 1.54 MB.
memory usage after 100,000,000 users: 1.54 MB.
Actual result:
--------------
memory usage after 100,000 users: 1.55 MB.
memory usage after 200,000 users: 1.57 MB.
memory usage after 300,000 users: 1.58 MB.
memory usage after 400,000 users: 1.59 MB.
memory usage after 500,000 users: 1.61 MB.
memory usage after 600,000 users: 1.62 MB.
memory usage after 700,000 users: 1.64 MB.
memory usage after 800,000 users: 1.65 MB.
memory usage after 900,000 users: 1.66 MB.
memory usage after 1,000,000 users: 1.68 MB.
[snip]
memory usage after 99,000,000 users: 18.43 MB.
memory usage after 99,100,000 users: 18.44 MB.
memory usage after 99,200,000 users: 18.46 MB.
memory usage after 99,300,000 users: 18.47 MB.
memory usage after 99,400,000 users: 18.48 MB.
memory usage after 99,500,000 users: 18.50 MB.
memory usage after 99,600,000 users: 18.51 MB.
memory usage after 99,700,000 users: 18.53 MB.
memory usage after 99,800,000 users: 18.54 MB.
memory usage after 99,900,000 users: 18.55 MB.
memory usage after 100,000,000 users: 18.57 MB.
------------------------------------------------------------------------
--
Edit this bug report at http://bugs.php.net/bug.php?id=48781&edit=1
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.