PHP的垃圾收集机制是否处理递归引用问题?

时间:2011-06-28 15:42:26

标签: php perl garbage-collection

在perl中,这将导致递归引用:

$a = \$a;

$a的引用计数永远不会再次来到0 ...

PHP有类似的问题吗?

如果没有,PHP gc如何处理它?<​​/ p>

3 个答案:

答案 0 :(得分:4)

从PHP 5.3.0开始,PHP的垃圾收集器可以收集包含周期的对象的图形。

请参阅PHP: Collecting Cycles

答案 1 :(得分:3)

PHP 5.3有一个新的垃圾收集器可以打破这种循环引用。在以前的版本中,自引用会导致内存泄漏并最终导致脚本死亡。 5.3可以打破参考并正确清理。

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

答案 2 :(得分:2)

PHP不是Perl。没有办法创建实际的内存引用,因为它从C指针中更为人所知。 “PHP中的引用是一种通过不同名称访问相同变量内容的方法。它们不像C指针;例如,您不能使用它们执行指针运算,它们不是实际的内存地址,[...] “What References Are)。

对于相同的内存地址的这种递归引用实际上不可能使用PHP引用和zval容器重现。因此,PHP GC不必处理perl摘录中显示的任何内容。

(如果您能够使用PHP实际创建这样的递归内存引用,我怀疑是可能的,请在您的问题中添加PHP示例代码。)

PHP中的递归引用

PHP中的递归引用如何?实际上,没有递归。在PHP中,“变量名称和变量内容是不同的”(What References Are),因此在引用该内存地址的内存地址值的意义上永远不会有真正的递归。

你可以获得的最高值是一个变量,它既是:别值一个值(标准变量)又是另一个同名值的别名:

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

输出:

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

它有一个值并引用它自身的值。

$a = &$a然后

的“垃圾收集”

对于那个PHP示例,垃圾收集如何发挥作用?答案根本不是。在$a = &$a这种情况下,实际上没有要解决的“循环”引用。它只是一个简单的容器,引用计数为1,同时作为引用。 PHP垃圾收集打开或关闭(或更好的运行周期集合与否)它没有任何区别。考虑$a = &$a案例的以下测试脚本:

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());

使用$collectCycles = false;$collectCycles = true;运行它并没有丝毫差别:

在没有收集周期的情况下运行:

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

以收集周期运行:

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

因此,出于性能原因和给定的示例,PHP看起来没有太多的垃圾收集,因为没有真正的循环引用。这只是一个价值和参考。取消设置别名是取消设置值和引用。根本不需要GC来处理循环引用。

PHP中的垃圾收集有用吗?

以下是演示代码示例,它引发了PHP在内存中的值不再可由变量(标签)访问的情况。这些值实际上是无用的,因为它们只存在于解释器级别,并且不再能被PHP代码访问。 PHP中的垃圾收集或多或少地处理这些值。

以下脚本将创建一个简单类的10个实例,该类只包含对自身的引用(在类的构造函数中设置)并且包含大约1兆字节大小的字符串。

每个实例都设置为相同的变量($instance)。在该标签旁边,该对象自我引用自身。因此正确地说出第一个变量值(具体实例)被标记为$this->self(私有成员)而第二个变量是$instance。由于$instance用作新值(下一个实例)的标签,因此仅保留当前实例中的标签。因此,该对象仍然存在。但是,它不再可以访问。

现在使用垃圾收集可以释放用于不再通过标签寻址的值的内存。这是通过gc_collect_cycles()完成的。

代码:

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";

输出:

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

此示例也显示实际实例仍然“生命”,因此对象内的代码仍然可以访问私有成员(如析构函数所示)。只有当垃圾收集器发挥作用时,实例才会被销毁。