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
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
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: Sun Oct 13 05:01:27 2024 UTC