Bug #69639 [Opn->Ver]: Closure causes memory leak (Cyclic reference) when used as class property
| From: | cmb@php.net | Date: | Fri, 15 May 2015 11:29:22 +0000 |
| Subject: | Bug #69639 [Opn->Ver]: Closure causes memory leak (Cyclic reference) when used as class property | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-192673@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=69639&edit=1
ID: 69639
Updated by: cmb@php.net
Reported by: tom at r dot je
Summary: Closure causes memory leak (Cyclic reference) when
used as class property
-Status: Open
+Status: Verified
Type: Bug
Package: Performance problem
Operating System: *
PHP Version: 5.6.9
Block user comment: N
Private report: N
New Comment:
As explained on the mentioned manual page, the garbage collection
is only triggered when the root buffer runs full. Its default size
is 10,000, so in your example the garbage collection is likely to
be never executed, because only 1,000 objects are created.
However, increasing the number of created objects by factor 100 to
100,000 and reducing the length of $largestr by the same factor,
results in the same behavior, what appears to be a bug.
Previous Comments:
------------------------------------------------------------------------
[2015-05-15 10:15:25] tom at r dot je
Description:
------------
Because closures inside classes implicitly have a reference to $this, if a closure is used as a
class property, this automatically creates a cyclic reference.
Having read the page on GC here: http://php.net/manual/en/features.gc.collecting-cycles.php
I can understand the problem, but the issue this causes is a memory leak any time a closure is used
as a class property (Or in an array/data structure in a class property)
Consider the following code:
<?php
class X {
private $largeStr;
private $closure;
public function foo() {
$this->largestr = str_repeat('ABC', 10000000);
$this->closure = function() {};
}
}
echo (memory_get_usage()/1024/1024) . 'mb<br >';
for ($i = 0; $i < 1000; $i++) {
$x = new X;
$x->foo();
unset($x);
echo (memory_get_peak_usage()/1024/1024) . 'mb<br >';
}
?>
Which outputs:
0.21446228027344mb
28.827682495117mb
57.438827514648mb
86.050003051758mb
114.66118621826mb
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 30000001 bytes)
I have explicitly called unset() which I would hope would free the memory used by the class, clearly
it doesn't. Similarly, adding
public function __destruct() {
unset($this->closure);
}
to the class does not help.
I'm not sure there is an obvious fix for this but gc_collect_cycles() in the loop does solve
it, I have to wonder why unset() doesn't just trigger gc_collect_cycles(). Presumably it can do
it in a smarter way as well because it doesn't have to look through every defined variable.
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=69639&edit=1