h2o*_*ooo 4 php garbage-collection
在经过多次追踪后,我终于弄明白我的代码出了什么问题,所以这个问题不是" 我该如何修复它 ",而是" 为什么会发生这种情况? ".
请考虑以下代码:
class Foo {
private $id;
public $handle;
public function __construct($id) {
$this->id = $id;
$this->handle = fopen('php://memory', 'r+');
echo $this->id . ' - construct' . PHP_EOL;
}
public function __destruct() {
echo $this->id . ' - destruct' . PHP_EOL;
fclose($this->handle);
}
public function bar() {
echo $this->id . ' - bar - ' . get_resource_type($this->handle) . PHP_EOL;
return $this;
}
public static function create($id) {
return new Foo($id);
}
}
Run Code Online (Sandbox Code Playgroud)
看起来很简单 - 创建它时会打开一个内存流并设置属性$handle和$id.在破坏它时将用于fclose关闭此流.
用法:
$foo = Foo::create(1); // works
var_dump( $foo->bar()->handle ); // works
var_dump( Foo::create(2)->bar()->handle ); // doesn't work
Run Code Online (Sandbox Code Playgroud)
这里的问题似乎是我期望两个调用返回完全相同但由于某种原因Foo::create(2),我不将实例保存到变量的调用return $this在bar()方法的一部分和我之间的某处调用垃圾收集器实际上使用该属性$handle.
如果你想知道,这是输出:
1 - construct // echo $this->id . ' - construct' . PHP_EOL;
1 - bar - stream // echo $this->id . ' - bar - ' ...
resource(5) of type (stream) // var_dump
2 - construct // echo $this->id . ' - construct' . PHP_EOL;
2 - bar - stream // echo $this->id . ' - bar - ' ...
2 - destruct // echo $this->id . ' - destruct' . PHP_EOL;
resource(6) of type (Unknown) // var_dump
1 - destruct // echo $this->id . ' - destruct' . PHP_EOL;
Run Code Online (Sandbox Code Playgroud)
从我可以看到这是发生的事情:
var_dump( Foo::create(2)->bar()->handle );
// run GC before continuing.. ^^ .. but I'm not done with it :(
Run Code Online (Sandbox Code Playgroud)
但为什么呢?为什么PHP认为我已经完成了变量/类实例,因此感觉需要破坏它?
演示:
这一切都归结为refcounts以及PHP如何以不同方式处理资源.
销毁类实例时,将关闭所有非数据库链接资源(请参阅上面的资源链接).其他地方引用的所有非资源仍然有效.
在您的第一个示例中,您指定$temp = Foo::create(1)将refcount增加到实例Foo,从而防止它被销毁,从而保持资源打开.
在你的第二个例子中var_dump( Foo::create(2)->bar()->handle );,这是事情的发展方式:
Foo::create(2)被调用,创建一个实例Foo.bar()在新实例上调用方法,返回$this将refcount增加1.bar()的范围,下一个动作不是方法调用或赋值,refcount减1.Unknown.作为额外的证明,这很好用:
$temp = Foo::create(3)->bar();
// $temp keep's Foo::create(3)'s refcount above zero
var_dump( $temp->handle );
Run Code Online (Sandbox Code Playgroud)
就像这样:
$temp = Foo::create(4)->bar()->bar()->bar();
// Same as previous example
var_dump( $temp->handle );
Run Code Online (Sandbox Code Playgroud)
还有这个:
// Assuming you made "id" public.
// Foo is destroyed, but "id" isn't a resource. It will be garbage collected later.
var_dump( Foo::create(5)->id );
Run Code Online (Sandbox Code Playgroud)
这不起作用:
$temp = Foo::create(6)->handle;
// Nothing has a reference to Foo, it gets destroyed, all resources closed.
var_dump($temp);
Run Code Online (Sandbox Code Playgroud)
这也不是:
$temp = Foo::create(7);
$handle = $temp->handle;
unset($temp);
// $handle is now a reference to a closed resource because Foo was destroyed
var_dump($handle);
Run Code Online (Sandbox Code Playgroud)
当Foo被破坏,所有打开的资源(除了数据库链接)关闭.引用其他属性Foo仍然有效.