php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #68132 Memory leakage while using anonymous functions
Submitted: 2014-10-02 13:55 UTC Modified: 2014-10-03 20:36 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:1 (100.0%)
From: nikola at petkanski dot com Assigned:
Status: Not a bug Package: Performance problem
PHP Version: 5.4.33 OS: Linux
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: nikola at petkanski dot com
New email:
PHP Version: OS:

 

 [2014-10-02 13:55 UTC] nikola at petkanski dot com
Description:
------------
This bug seem like a duplicate of an earlier one that got marked as fixed:

- https://bugs.php.net/bug.php?id=60139

It has been tried on the following configurations and the problem persists:

PHP 5.4.32 (cli) (built: Aug 22 2014 07:07:38)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies

PHP 5.5.9-1ubuntu4.4 (cli) (built: Sep  4 2014 06:56:34) 
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend OPcache v7.0.3, Copyright (c) 1999-2014, by Zend Technologies
    with Xdebug v2.2.3, Copyright (c) 2002-2013, by Derick Rethans


For convenience I'm gonna copy the description of the original bug as it seem relevant and I guess the problem is the same:
-----

In the following Script 1 and Script 2, the created objects are not destructed until the engine shutdowns because of a reference cycle.

The objects hold a reference to an anonymous function that itself hold a reference to the object.

It seems that the garbage collector is unable to break the cycle.

Script 3 doesn't leak memory because the anonymous function doesn't hold a reference to the object.

Test script:
---------------
# Script 1 (leaks)

<?php

class Foo
{
    public $x;

    public function __construct() {
        $this->x = function() {};
    }
}

echo memory_get_usage(), "\n";

for ($i = 0; $i < 100000; ++$i) {
    new Foo;
}
gc_collect_cycles();

echo memory_get_usage(), "\n";
?>

# Script 2 (leaks)

<?php

class Foo
{
    public $x;

    public function __construct() {
        $self = $this;
        $this->x = function() use ($self) {};
    }
}

echo memory_get_usage(), "\n";

for ($i = 0; $i < 100000; ++$i) {
    new Foo;
}
gc_collect_cycles();

echo memory_get_usage(), "\n";

?>

# Script 3 (does not leak)

<?php

class Foo
{
    public $x;

    public function __construct() {
        $this->x = get_fun();
    }
}

function get_fun() {
    return function() {};
}

echo memory_get_usage(), "\n";

for ($i = 0; $i < 1000; ++$i) {
    new Foo;
}
gc_collect_cycles();

echo memory_get_usage(), "\n";
?>

Expected result:
----------------
Memory usage before and after is approximately the same:

nikola@nikola-450G1:~$ php script3.php 
237944
238152


Actual result:
--------------
Objects are never freed and memory usage increases:

nikola@nikola-450G1:~$ php script1.php 
237296
1220536

nikola@nikola-450G1:~$ php script2.php 
237848
1221256

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2014-10-03 20:36 UTC] nikic@php.net
-Status: Open +Status: Not a bug
 [2014-10-03 20:36 UTC] nikic@php.net
There is no real leak here - which is easy to see if you adjust the the number of iterations. The memory usage will stay the same.

What you see are retained object store buckets, see https://bugs.php.net/bug.php?id=33487. The GC runs once the root buffer with 10k elements fills up, at which point the object store uses 2^14 buckets.

You can see that this is about right, because (1220536-237296)/(16384*8*8) == 0.93 (8*8 is the size of an object store bucket).

In PHP 7 the memory difference will be 8 times smaller, so it doesn't really matter anymore.
 [2014-10-03 20:38 UTC] nikic@php.net
More accurate calculation: (1220536-237296)/((16384-1024)*8*8) == 1.0002, where 1024 is the initial object store size.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Wed Nov 06 15:01:29 2024 UTC