PHP';的垃圾收集机制处理递归引用问题


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

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

$a = '$a;

并且$a的引用计数将永远不会再出现在0。。。

PHP也有类似的问题吗?

如果没有,PHP gc如何处理它?

从PHP 5.3.0开始,PHP的垃圾收集器可以并且收集包含循环的对象的图。

请参阅PHP:收集周期

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

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

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

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

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

PHP中的递归引用

PHP中的递归引用是什么样子的?事实上,没有递归。在PHP中,"变量名称和变量内容不同"(引用是什么),因此在内存地址值引用该内存地址的意义上永远不会有真正的递归。

你能得到的最高值是一个既有值别名(标准变量)又有相同值别名的变量:

$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

此示例还表明,实际实例仍然"存在",因此对象内部的代码仍然可以访问私有成员(如析构函数所示)。只有当垃圾收集器发挥作用时,实例才会被销毁。