PHP 擁有自動垃圾回收機制(GC),開發者通常無需手動管理記憶體。但在特定情況下——物件間存在循環參考——記憶體將無法正常釋放,導致長期運行腳本(如 CLI Daemon、Queue Worker)記憶體耗盡。
<?php
class Foo {
public function __construct() {
$this->bar = new Bar($this); // Foo 參考 Bar,並傳遞 $this 給 Bar
}
}
class Bar {
public function __construct($foo = null) {
$this->foo = $foo; // Bar 參考回 Foo
}
}
// 無限迴圈測試
while (true) {
$foo = new Foo();
unset($foo); // 理應釋放記憶體,但無效!
echo number_format(memory_get_usage()) . "\n";
}
?>
33,551,616 33,551,976 33,552,336 33,552,696 PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 16 bytes) in memleak.php on line 17
unset($foo),內部循環參考仍存在,refcount 不歸零在物件銷毀前,透過 __destruct() 主動解除內部參考:
<?php
class Foo {
public function __construct() {
$this->bar = new Bar($this);
}
public function __destruct() {
unset($this->bar); // 關鍵:斷開對 Bar 的參考
}
}
class Bar {
public function __construct($foo = null) {
$this->foo = $foo;
}
}
while (true) {
$foo = new Foo();
$foo->__destruct(); // 手動觸發解構器
unset($foo);
echo number_format(memory_get_usage()) . "\n"; // 記憶體穩定
}
?>
| 場景 | 建議做法 |
|---|---|
| 父子物件關係 | 父物件持有子物件,子物件避免持有父物件;若必要,用 WeakReference (PHP 7.4+) |
| 觀察者模式 | 主體存放觀察者時使用 SplObjectStorage 或弱引用 |
| 長期運行腳本 | |
| 大型物件圖 | 實作 __destruct() 明確清理內部引用 |
zval 結構優化減少記憶體佔用WeakReference 類別,支援弱引用,完美解決循環參考<?php
class Foo {
public function __construct() {
$this->bar = new Bar(new WeakReference($this));
}
}
class Bar {
public function __construct(WeakReference $fooRef) {
$this->fooRef = $fooRef;
}
public function getFoo() {
return $this->fooRef->get(); // 可能回傳 null 若 Foo 已被銷毀
}
}