首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 物件循環參考導致記憶體洩漏:成因與解決方案

     編輯:PHP 綜合知識

PHP 記憶體管理陷阱:循環參考與垃圾回收機制

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

根本原因分析

PHP 參考計數機制

  • 每個變數/物件擁有 refcount(參考計數器)
  • 當 refcount 歸零 → 記憶體可被釋放
  • 循環參考:Foo→Bar、Bar→Foo,雙方 refcount 永遠 ≥1
  • 即使外部變數 unset($foo),內部循環參考仍存在,refcount 不歸零
  • PHP 5.3 前無循環垃圾回收器,記憶體永不釋放

用戶層解決方案:手動斷開循環

在物件銷毀前,透過 __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"; // 記憶體穩定
}
?>

最佳實踐建議

  • 定期呼叫 gc_collect_cycles() 強制回收
  • 監控 memory_get_usage() 設定告警
  • 場景 建議做法
    父子物件關係 父物件持有子物件,子物件避免持有父物件;若必要,用 WeakReference (PHP 7.4+)
    觀察者模式 主體存放觀察者時使用 SplObjectStorage 或弱引用
    長期運行腳本
    大型物件圖 實作 __destruct() 明確清理內部引用

    PHP 版本演進與 GC 改進

    • PHP 5.2 及之前:無循環 GC,循環參考必漏記憶體
    • PHP 5.3:引入 同步循環垃圾回收器,自動檢測並清理循環參考
    • PHP 7.0+:GC 效能大幅提升,zval 結構優化減少記憶體佔用
    • PHP 7.4+:新增 WeakReference 類別,支援弱引用,完美解決循環參考

    現代寫法:使用 WeakReference (PHP 7.4+)

    <?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 已被銷毀
        }
    }
    
    1. 上一篇文章:
    2. 下一篇文章:
    Copyright © 程式碼之家 All Rights Reserved