萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 5.2 vs 5.3 垃圾回收機制演進:從引用計數到同步循環回收

PHP 是托管語言,記憶體管理由引擎自動完成。PHP 5.2 到 5.3 的 GC 演進,解決了長期困擾長運行腳本的**循環引用記憶體洩漏**問題。

統一內部結構:zval

所有 PHP 變數在 C 層統一用 zval (_zval_struct) 表示:

struct _zval_struct {
    zvalue_value value;        // 聯合體:lval/dval/str/ht/obj
    zend_uint refcount__gc;    // 引用計數 (GC 核心)
    zend_uchar type;           // 型別標記
    zend_uchar is_ref__gc;     // 是否為引用 (&)
};

refcount__gc 記錄有多少變數/符號表指向此 zval。

PHP 5.2:單純引用計數

  • 建立變數 → refcount=1
  • 賦值/傳參 → refcount++
  • unset/離開作用域 → refcount--
  • **GC 觸發:請求結束 (RSHUTDOWN) 統一銷毀 refcount=0 的 zval**

致命缺陷:循環引用無法回收

$a = [];
$a[] = &$a; // $a[0] 引用 $a 自己
unset($a);  // 全域符號表移除 $a,但 $a 的 zval refcount=1 (被自己引用)
// 此 zval 永遠不為 0 → 記憶體洩漏,直到進程結束

短生命週期 Web 請求 (毫秒級) 掩蓋了此問題;但 **CLI 長運行腳本、Daemon、Worker** 會累積洩漏導致 OOM。

PHP 5.3:同步循環回收

基於 IBM 論文 Concurrent Cycle Collection in Reference Counted Systems 實作。

核心機制

  1. 根緩衝區:固定大小 (預設 10,000) 陣列,存放「可能根」 (refcount > 0 且為複雜類型:陣列/物件)。
  2. 觸發時機: 根緩衝區**滿**時,或手動呼叫 gc_collect_cycles()。
  3. 三階段演算法:
    1. 標記減計: 從每個根 DFS 遍歷可達 zval,**每個 zval refcount -1**,並標記「已減」。避免重複減 (多根可達同一 zval)。
    2. 掃描加回: 再次 DFS,若 zval refcount ≠ 0 → **refcount +1** (恢復);若 = 0 → 保持 0 (確認是垃圾)。
    3. 清理: 清空根緩衝區,**銷毀所有 refcount=0 的 zval**,釋放記憶體。

關鍵特性

  • **非即時**:不每次 refcount 變化就掃描,批次處理,低開銷。
  • **解決循環引用**:循環結構內部引用相互抵消,最終 refcount 歸零被回收。
  • **洩漏上界可控**:最多佔用「根緩衝區大小 × 平均對象大小」的記憶體。
  • **可調參數: zend.enable_gc (開關)、gc_collect_cycles() (強制)、源碼修改 GC_ROOT_BUFFER_MAX_ENTRIES 重編譯調整緩衝區大小。

性能影響實測 (官方數據)

  • **記憶體洩漏測試**:5.3 GC 啟用後,長運行腳本記憶體曲線平穩;5.2 或關閉 GC 則線性增長直至耗盡。
  • **CPU 開銷**:GC 觸發時有短暫 CPU 尖峰,但整體吞吐量影響 < 5% (視循環複雜度)。
  • **建議: CLI 長腳本**保持 GC 開啟**,必要時主動 gc_collect_cycles() (如批次處理每 1000 條)。Web 請求生命週期短,影響可忽略。

PHP 7/8 進一步優化

  • zval 不再單獨分配,直接內嵌於 HashTable/物件/陣列槽位,**大幅減少分配開銷**。
  • GC 邏輯不變,但遍歷速度更快 (緊湊記憶體佈局、CPU 緩存友好)。
  • PHP 8.1+ 引入 WeakMap/WeakReference,支援弱引用,進一步減少循環引用風險。

開發者實踐建議

  1. 理解 **Copy-on-Write**:$b = $a 不複製內存,寫入時才複製,善用此特性避免不必要 &。
  2. 大陣列/物件在循環中**及時 unset** 或重賦值 $arr = [] 釋放引用。
  3. 避免人為製造循環引用 (如雙向關聯未用弱引用)。
  4. 監控 memory_get_usage()、gc_status() (PHP 7.3+) 觀察 GC 行為。
  5. 極端記憶體敏感場景可 ini_set('zend.enable_gc', 0) 關閉 GC,**但必須確保無循環引用**,否則洩漏不可逆。

從「請求結束大掃除」到「運行期增量循環清理」,PHP GC 演進讓長運行 PHP 應用成為現實。

copyright © 萬盛學電腦網 all rights reserved