Je Rog
Je Rog

Reputation: 5991

Does PHP's garbage collection mechanism handle recursive reference issue?

In perl this will cause recursive reference :

$a = \$a;

And $a's reference count will never come to 0 again...

Does PHP has similar issue?

If not,how does PHP gc handles it?

Upvotes: 2

Views: 615

Answers (3)

hakre
hakre

Reputation: 197832

PHP is not Perl. There is no way to create actual memory references as it's more known from C pointers. "References in PHP are a means to access the same variable content by different names. They are not like C pointers; for instance, you cannot perform pointer arithmetic using them, they are not actual memory addresses, [...]" (What References Are).

Such an recursive reference on to the same memory address is actually not possible to reproduce with PHP references and zval containers. Therefore PHP GC does not have to deal with anything like shown in the perl excerpt.

(If you're able to actually create such a recursive memory reference with PHP which I doubt is possible, please add the PHP example code to your question.)

Recursive References in PHP

How does a recursive reference in PHP looks like? In fact, there is no recursion. In PHP "variable name and variable content are different" (What References Are), so there never is true recursion in sense of a memory address' value referencing that memory address.

The highest you can get is a variable that is both: aliasing a value (standard variable) and being additionally an alias to the same value again:

$a = 'value';
$a = &$a;
xdebug_debug_zval('a');

Output:

a: (refcount=1, is_ref=1)='value'

It has a value and references that value of itself.

"Garbage Collection" of $a = &$a then

So for that PHP example, how does garbage collection come into play? The answer is not at all. There is actually no "cyclic" reference to resolve in this $a = &$a case. It's just a simple container with a reference count of one while being a reference. PHP garbage collection on or off (or better running cycle collection or not) it does not make any difference. Considering the following test-script for the $a = &$a case:

function alocal($collectCycles) {
    $a = str_repeat('a', 1048576);

    $collectCycles && gc_collect_cycles();
    var_dump(xdebug_memory_usage());

    $a = &$a;

    $collectCycles && gc_collect_cycles();
    var_dump(xdebug_memory_usage());

    xdebug_debug_zval('a');
}

$collectCycles = false; // or true

$collectCycles ? gc_enable() : gc_disable();

$collectCycles && gc_collect_cycles();
var_dump(xdebug_memory_usage());

alocal($collectCycles);

$collectCycles && gc_collect_cycles();
var_dump(xdebug_memory_usage());

Running it with $collectCycles = false; or $collectCycles = true; does not make the slightest difference:

Running without collecting cycles:

int(660680)
int(1709328)
int(1709328)
a: (refcount=1, is_ref=1)='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...'
int(660848)

Running with collecting cycles:

int(660680)
int(1709328)
int(1709328)
a: (refcount=1, is_ref=1)='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...'
int(660848)

So for performance reasons and the given example, it looks like that there is not much garbage collection involved in PHP which makes sense as there is no true cyclic reference. It's just a value and a reference. Un-setting the alias is un-setting both, the value and the reference. No need for a GC to deal with cyclic references at all.

For what is Garbage Collection in PHP useful?

The following is a demonstration code example that provokes the situation where PHP will have values in memory that are not accessible by a variable (label) any longer. These values are actually useless then because they exist only on the interpreter level and can not be accessed by PHP code any longer. Garbage collection in PHP deals with these values more or less only.

The following script will create 10 instances of a simple class that only contains a reference to itself (set in the class' constructor) and that contains a string of about one megabyte size.

Each of the instances is set to the same variable ($instance). Next to that label, the object self-references itself. Therefore correctly spoken the first variable the value (the concrete instance) is labeled as is $this->self (the private member) and the second variable is $instance. As $instance functions as a label to a new value (the next instance), only the label inside the current instance is left. Therefore the object still exists. However, it's not accessible any longer.

Using garbage collection now can free the memory used for values that are not addressable via labels any longer. This is done with gc_collect_cycles().

The code:

define('CONSUME_PER_INSTANCE', 1048576); // in bytes

class StoreMore
{
    static $counter;
    private $self;
    private $store;
    private $number;
    public function __construct() {
        // assign object instance to a private member of itself (self-reference):
        $this->self = $this;
        $this->store = str_repeat('a', CONSUME_PER_INSTANCE); // consume some memory
        $this->number = ++ self::$counter;
    }
    public function __destruct() {
        echo 'Instance #', $this->number, ' destructed.', "\n";
    }
}

$baseMem = xdebug_memory_usage();

echo 'Memory use on start: ', number_format($baseMem, 0, '', ' '), "\n";

for($i=0;$i<10;$i++) {
    $instance = new StoreMore();
}

$diffMem = xdebug_memory_usage() - $baseMem;

echo 'Memory afterall: ', number_format(xdebug_memory_usage(), 0, '', ' '), ' - Diff: ', number_format($diffMem, 0, '', ' '), "\n";
echo 'Rough number of "instances" in afterall Diff: ', (int)($diffMem / CONSUME_PER_INSTANCE), "\n";


echo 'Garbage collecting starting...', "\n";
$result = gc_collect_cycles();
echo 'Garbage collecting ended: ', $result, "\n";

echo 'Memory after gc: ', number_format(xdebug_memory_usage(), 0, '', ' '), ' - Diff: ', number_format(xdebug_memory_usage() - $baseMem, 0, '', ' '), "\n";

unset($instance); // remove last instance

$lastDiffMem = xdebug_memory_usage() - $baseMem;

echo 'Memory after removal of last instance: ', number_format(xdebug_memory_usage(), 0, '', ' '), ' - Diff: ', number_format($lastDiffMem, 0, '', ' '), "\n";
echo 'Rough number of "instances" in last instance Diff: ', (int)($lastDiffMem / CONSUME_PER_INSTANCE), "\n";

echo 'Garbage collecting starting...', "\n";
$result = gc_collect_cycles();
echo 'Garbage collecting ended: ', $result, "\n";


echo 'Memory after final gc: ', number_format(xdebug_memory_usage(), 0, '', ' '), ' - Diff: ', number_format(xdebug_memory_usage() - $baseMem, 0, '', ' '), "\n";

The output:

Memory use on start: 695 600
Memory afterall: 11 188 800 - Diff: 10 493 056
Rough number of "instances" in afterall Diff: 10
Garbage collecting starting...
Instance #1 destructed.
Instance #2 destructed.
Instance #3 destructed.
Instance #4 destructed.
Instance #5 destructed.
Instance #6 destructed.
Instance #7 destructed.
Instance #8 destructed.
Instance #9 destructed.
Garbage collecting ended: 27
Memory after gc: 1 745 496 - Diff: 1 049 896
Memory after removal of last instance: 1 745 552 - Diff: 1 049 800
Rough number of "instances" in last instance Diff: 1
Garbage collecting starting...
Instance #10 destructed.
Garbage collecting ended: 2
Memory after final gc: 696 328 - Diff: 728

This example shows as well, that the actual instance still "lives", so code inside the object can still access private members (as the destructor shows). The instances will be destroyed only if the garbage collector comes into play.

Upvotes: 2

user703016
user703016

Reputation: 37955

As of PHP 5.3.0, PHP's Garbage collector can and will collect graphs of objects that contain cycles.

See PHP: Collecting Cycles

Upvotes: 4

Marc B
Marc B

Reputation: 360702

PHP 5.3's got a new garbage collector that can break such circular references. In previous versions, the self-reference would cause a memory leak and eventually kill the script. 5.3 can break the reference and clean up properly.

http://www.php.net/manual/en/features.gc.collecting-cycles.php

Upvotes: 3

Related Questions