我试图在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中都观察到了这种行为。
据我所知,对于同一对象,析构函数将不会被调用两次。重用析构函数中的对象通常是一个坏习惯。销毁了析构函数的对象应该销毁,而不可以重用。在您的测试脚本中,它不会引起任何严重的问题,但是在现实生活中,如果开发人员不注意,这种用法可能会导致意外行为。
起初,当我阅读您的问题时,我担心您造成了内存泄漏,但事实并非如此。仅当尚未在此对象上调用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,该对象已经处于销毁状态。