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

PHP 垃圾回收機制深度解析:zval 結構、循環引用、GC 演算法與長運行腳本優化

     編輯:PHP 相關知識

前言:為什麼 PHP 開發者要懂 GC?

PHP 5.3 引入專門的 循環垃圾回收器 (Cyclic GC) 解決參考計數無法處理的循環引用洩漏。對於長運行腳本 (守護進程、Queue Worker、CLI 批次任務),理解 GC 行為關係到記憶體穩定與效能。

核心資料結構:zval

每個 PHP 變數在引擎中由 zval 容器儲存 (PHP 7 後為 zend_value + zend_refcounted):

struct zval {
    zend_value        value;   // 實際資料 (整數、雙精度、字串指標、陣列指標、物件指標...)
    zend_refcounted  *ref;    // 參考計數與型別資訊 (GC 相關)
    // PHP 7+ 合併為單一結構,概念不變
};

關鍵欄位:

  • refcount:指向此容器的變數/符號數。
  • is_ref:是否為 & 參考 (而非複製)。
  • type:IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_REFERENCE 等。

Copy-on-Write (COW) 機制

<?php
$a = 'hello';      // refcount=1, is_ref=0
$b = $a;           // refcount=2 (共享同一 zval)
$b .= ' world';    // 寫入觸發分離:$b 新建 zval, $a 保持不變, refcount 各自=1

參考賦值 (&) 會將 is_ref=1,強制共享同一容器,寫入直接影響所有綁定變數。

循環引用與記憶體洩漏 (PHP 5.2 之前無解)

<?php
$a = ['one'];
$a[] = &$a;        // $a[1] 參考 $a 自身 → 循環
unset($a);         // 符號表移除 $a,但容器 refcount=1 (來自 $a[1]),無法釋放
// 請求結束前記憶體永不釋放 → 長運行腳本洩漏

PHP 5.3+ 循環 GC 演算法 (簡化版)

  1. 根緩衝區:固定大小陣列 (預設 10,000 個 zval*),儲存「可能成為垃圾」的容器指標。
  2. 觸發條件:
    • refcount 增加 → 非垃圾,不進緩衝區。
    • refcount 減為 0 → 立即釋放,不進緩衝區。
    • refcount 減少但 > 0 → 可能形成循環,放入根緩衝區 (紫色標記)。
  3. 緩衝區滿或手動觸發 (gc_collect_cycles()) → 執行 標記-掃描:
    • 標記階段:從根緩衝區出發,深度優先遍歷所有可達容器,將 refcount -1 (灰色),再遞歸遍歷屬性/元素,最終 refcount 為 0 的為垃圾 (白色),>0 的為存活 (黑色)。
    • 掃描階段:釋放所有白色容器記憶體,恢復灰/黑色容器 refcount。

GC 設定與控制

設定說明預設
zend.enable_gcphp.ini 啟用/停用 GCOn
gc.collect_cycles()強制執行循環回收,回傳釋放數量-
gc_enable() / gc_disable()執行期動態開關On
gc_status()PHP 7.3+ 取得 GC 狀態 (runs, collected, thresholds)-

效能影響實測 (PHP 8.2)

<?php
class Foo { public $var = '3.14159'; public $self; }

$base = memory_get_usage();
for ($i = 0; $i < 1_000_000; $i++) {
    $a = new Foo();
    $a->self = $a; // 循環引用
    if ($i % 100_000 === 0) {
        echo "$i: " . (memory_get_usage() - $base) . " bytes\n";
    }
}
echo "Peak: " . memory_get_peak_usage() . "\n";

結果:啟用 GC 記憶體穩定在 ~30MB;停用 GC 線性增長至 >1GB 導致 OOM。執行時間增加 ~5-7% (GC 執行開銷)。

長運行腳本最佳實務

  1. 定期主動收集:每處理 N 筆資料呼叫 gc_collect_cycles(),避免緩衝區滿時大量掃描造成延遲尖峰。
  2. 避免大型循環結構:樹狀結構用 WeakReference (PHP 7.4+) 打破父子循環;觀察者模式用 SplObjectStorage + 弱引用。
  3. 監控 GC 指標:定期記錄 gc_status(),觀察 collected / runs 比率,調整 zend.gc_probability (PHP 8.1+)。
  4. 分批處理:大量資料分批次,批次間 unset() 大變數並 gc_collect_cycles()。

PHP 8 新特性:WeakMap、WeakReference

<?php
class Cache {
    private WeakMap $map; // Key 為物件,不增加 refcount
    public function __construct() { $this->map = new WeakMap(); }
    public function set(object $key, mixed $val): void { $this->map[$key] = $val; }
    public function get(object $key): mixed { return $this->map[$key] ?? null; }
}
// 當 $key 無其他參考時,自動從 WeakMap 移除,不阻礙 GC

完整效能測試腳本與 GC 視覺化工具見 GitHub。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved