我试图在CLI程序中建立一个类似于“池”的结构,其中包括大量的“借款”和“回收”。在测试的过程中,我遇到了一些意想不到的事情:
<?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,7278中的2911和1038 ),它的__destruct()将不会被调用;相反,它将简单地消失。
我很困惑。为什么会发生这种事?为什么每次都不给FOO->__destruct打电话?是否有可能使FOO自动回收自身的实例?
我正在PHP-7.2中测试这一点,这是在Windows和WSL中都可以观察到的行为。
发布于 2019-12-25 15:41:15
据我所知,对于同一个对象,析构函数不会被调用两次。从析构函数中重用对象通常是一种不好的做法。应该销毁而不是重用其析构函数的对象。在您的测试脚本中,它不会引起任何严重的问题,但是在现实生活中,如果开发人员不小心,这种使用可能会导致意想不到的行为。
起初,当我读到你的问题时,我担心你造成了内存泄漏,但事实并非如此。当您离开Bar()的作用域时,析构函数才会被调用,只有在这个对象上还没有调用它的时候。但是,由于保存引用并在ref_count中增加该对象的__destruct(),垃圾收集器还不能收集对象。它必须等到下一次当您减少这个对象的ref_count时。
这一过程可解释如下(简化):
Bar()函数,它创建FOO的一个实例。创建一个对象并将其分配给$foo。(ref_count = 1) Bar()的末尾--唯一对该对象的引用丢失(ref_count = 0),这意味着PHP启动了销毁对象的过程。2.1。破坏者被称为。在析构函数中,将ref_count增加到1。
2.2。下一步是GC收集对象,但ref_count不是零。这可能意味着一个循环,或者在您的示例中,已经在析构函数中创建了一个新的引用。GC必须等待,直到没有非周期引用来收集对象。你又叫
Bar()。您将相同的对象从静态数组中移除,这意味着FOO::$pool中的引用消失了,但您立即将其分配给$foo。(ref_count = 1)
Bar()的末尾--对这个对象的唯一引用丢失了(ref_count = 0),这意味着GC最终可以收集这个对象。析构函数已经被调用,因此这里没有其他的操作。您没有回收对象,您只是延长了它在内存中的存在。您可以再访问它一段时间,但是对于PHP,该对象已经在被销毁的过程中。
https://stackoverflow.com/questions/59479395
复制相似问题