PHP 內核使用 zval 結構體存儲變量,包含:值、類型、refcount(引用計數)、is_ref(是否屬於引用集合)。安裝 xdebug 後可用 xdebug_debug_zval() 觀察。
<?php
$str = "jb51.net";
xdebug_debug_zval('str');
?>
輸出:
str: (refcount=1, is_ref=0), string 'jb51.net' (length=10)
當 refcount 歸零時,zval 被銷毀。unset() 使對應 zval 的 refcount - 1。
<?php
$a = "aaa";
$b = & $a; // 建立引用,is_ref=1, refcount=2
unset($a); // 刪除 $a 符號表項,refcount 變 1
xdebug_debug_zval("b"); // b 依然存在且值為 aaa
?>
輸出:
b: (refcount=1, is_ref=0), string 'aaa' (length=3)
關鍵點:unset($a) 只移除符號表關聯,不銷毀值本身(因 $b 仍引用)。
<?php
$arr = array('a' => 'aaa', 'b' => "bbb");
xdebug_debug_zval('arr');
$arr['aaa'] = $arr['a']; // 將 'a' 的值賦給新鍵 'aaa'
xdebug_debug_zval('arr');
?>
第一次輸出:
arr: (refcount=1, is_ref=0), array 'a' => (refcount=1, is_ref=0), string 'aaa' (length=3) 'b' => (refcount=1, is_ref=0), string 'bbb' (length=3)
第二次輸出:
arr: (refcount=1, is_ref=0), array 'a' => (refcount=2, is_ref=0), string 'aaa' (length=3) 'b' => (refcount=1, is_ref=0), string 'bbb' (length=3) 'aaa' => (refcount=2, is_ref=0), string 'aaa' (length=3)
'a' 與新元素 'aaa' 指向同一個 zval 容器 (refcount=2)| 版本 | 機制 | 循環引用處理 |
|---|---|---|
| PHP 5.2 | 簡單引用計數 | 無法回收循環引用 → 記憶體洩漏 |
| PHP 5.3+ | 引用計數 + 同步演算法 | 定期掃描根緩衝區,檢測並清理循環引用 |
<?php
$a = array();
$a[] = &$a; // 陣列包含自己,形成循環引用
unset($a); // PHP 5.2: 內存洩漏;PHP 5.3+: GC 會清理
?>
<?php
// 開啟/關閉 GC (預設開啟)
ini_set('zend.enable_gc', 1);
// 強制執行垃圾回收
gc_collect_cycles();
// 獲取 GC 狀態
var_dump(gc_enabled()); // bool(true)
// 獲取根緩衝區資訊
print_r(gc_status()); // PHP 7.3+
?>
&$var 避免複製,但注意生命週期管理gc_collect_cycles() 主動清理memory_get_usage() 追蹤內存變化