重用对象将不会调用__destruct`?

问题描述 投票:1回答:1

我试图在CLI程序中创建一个类似于“ pool”的结构,其中包括许多“借用”和“回收”。在测试时,我遇到了一些出乎意料的事情:

<?php
class FOO
{
    public static $pool=[];
    public static function get()
    {
        if(empty(self::$pool))
        {
            self::$pool[]=new self(mt_rand(1000,9999));
        }
        return array_shift(self::$pool);
    }
    protected static function recycle(FOO $foo)
    {
        echo "Recycling {$foo->num}\n";
        self::$pool[]=$foo;
    }

    public $num;
    protected function __construct(int $num)
    {
        $this->num=$num;
    }
    public function __destruct()
    {
        static::recycle($this);
    }
}
function Bar()
{
    $foo=FOO::get();
    echo "Got {$foo->num}\n";
}
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
echo "Bar\n";
Bar();
print_r(FOO::$pool);
echo "End.\n";

输出为:

Bar
Got 2911
Recycling 2911
Bar
Got 2911
Bar
Got 1038
Recycling 1038
Bar
Got 1038
Array
(
)
End.

如果将Bar()调用限制为3次而不是4次,则会得到以下输出:

Bar
Got 7278
Recycling 7278
Bar
Got 7278
Bar
Got 6703
Recycling 6703
Array
(
    [0] => FOO Object
        (
            [num] => 6703
        )

)
End.

在这里您可以看到,当一个对象被“重用”时(请参见示例1中的2911和1038,示例2中的7278),将不会调用其__destruct();相反,它只会消失。

我很困惑。为什么会这样?为什么FOO->__destruct不会每次都被呼叫?是否可以使FOO实例自动回收?

我正在PHP-7.2中对此进行测试,在Windows和WSL-Ubuntu中都观察到了这种行为。

php destructor
1个回答
0
投票

据我所知,对于同一对象,析构函数将不会被调用两次。重用析构函数中的对象通常是一个坏习惯。销毁了析构函数的对象应该销毁,而不可以重用。在您的测试脚本中,它不会引起任何严重的问题,但是在现实生活中,如果开发人员不注意,这种用法可能会导致意外行为。

起初,当我阅读您的问题时,我担心您造成了内存泄漏,但事实并非如此。仅当尚未在此对象上调用Bar()的作用域时,析构函数才会被调用。但是,由于您保存了引用并在ref_count中增加了该对象的__destruct(),因此垃圾收集器还无法收集该对象。当您降低该对象的ref_count时,它必须等到下一次。

该过程可以解释如下(简化):

  1. 您调用Bar()函数,该函数创建FOO的实例。创建一个对象并将其分配给$foo。 (ref_count = 1
  2. Bar()末尾,仅丢失了对该对象的引用(ref_count = 0),这意味着PHP开始破坏对象的过程。2.1。析构函数被调用。在析构函数内部,将ref_count增加到1。2.2。下一步将是GC收集对象,但ref_count不为零。这可能意味着一个循环,或者像您的示例一样,在析构函数中创建了一个新引用。 GC必须等待,直到没有非循环引用来收集对象为止。
  3. 您再次呼叫Bar()。您将同一对象从静态数组中移出,这意味着FOO::$pool内部的引用已消失,但您立即将其分配给$foo。 (ref_count = 1
  4. Bar()末尾唯一丢失了对该对象的引用(ref_count = 0),这意味着GC最终可以收集该对象。析构函数已经被调用,因此这里没有其他动作。

您没有回收该对象,只是延长了它在内存中的存在时间。您可以访问它一段时间,但是对于PHP,该对象已经处于销毁状态。

© www.soinside.com 2019 - 2024. All rights reserved.