Bug #69639 [Opn->Ver]: Closure causes memory leak (Cyclic reference) when used as class property

From: 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

« previous php.bugs (#192673) next »