PHP 使用 引用计数(refcount) + 循环检测机制 来管理内存。在大多数情况下,引用计数机制可以自动释放内存,但循环引用(circular reference是一个特殊的陷阱,PHP 通过“垃圾收集器 GC”来专门处理它。
✅ 一句话概括:
PHP 在发现变量间的引用形成“闭环”且引用计数永远大于 0 时,会启用垃圾收集器(GC)扫描“根缓冲区”中的循环引用,并安全释放它们。
🧠 什么是循环引用?(示例)
class Node {
public $child;
}
$a = new Node();
$b = new Node();
$a->child = $b;
$b->child = $a; // 🔁 循环引用
此时 $a
和 $b
各自都持有对方的引用,refcount 永远是 1+。
- 正常引用:refcount → 到 0 就自动释放
- 循环引用:即使你 unset($a)、unset($b),它们的 refcount 也不为 0
这时就需要 垃圾收集器(GC) 出马。
🧪 引用计数不能解决的地方
$a = new Node();
$b = new Node();
$a->child = $b;
$b->child = $a;
unset($a, $b); // refcount 仍然 > 0,不会立即释放!
这两个对象互相引用,refcount 永远为 1,无法自然释放。PHP 就会:
✅ Step 1:检测是否可能为循环
PHP 会把可能有问题的变量放入“根缓冲区(Root buffer)”。
✅ Step 2:垃圾收集器启动(自动 or 手动)
- 默认在脚本执行过程中自动启动
- 也可以手动触发:
gc_collect_cycles();
✅ Step 3:标记 - 清除算法
GC 会:
- 标记所有可能是循环引用的变量
- 检查这些变量是否能“逃脱”内存(是否有从外部指向它们的引用)
- 如果没有,它们就是“孤岛” → 可以安全释放
🧪 内部实现机制(简化)
- PHP 使用
zval
存储变量,每个zval
有refcount
和is_ref
- 额外结构:
gc_root_buffer
用于记录循环引用候选 - GC 使用 三色标记算法(tricolor marking) 类似方法检测和清理:
白色:可能要清理的对象
灰色:已访问但还没确认清理的对象
黑色:确定不清理的对象
🔧 如何手动控制 GC 行为
gc_enable(); // 启用 GC(默认启用)
gc_disable(); // 禁用 GC
gc_collect_cycles(); // 手动触发一次 GC 回收
gc_enabled(); // 检查是否启用 GC
🔍 查看 GC 状态
print_r(gc_status());
输出示例:
Array
(
[runs] => 3 // 已触发次数
[collected] => 2 // 已清理变量个数
[threshold] => 10000
[roots] => 0
)
🛡️ 性能建议
- PHP 的 GC 性能一般无需人工干预,但频繁创建循环引用对象(如大型 ORM、树状结构)时要注意内存泄漏
SplObjectStorage
等结构体也可能产生循环
✅ 总结核心机制
机制 | 说明 |
---|---|
引用计数 | 大多数情况下够用 |
循环引用 | 会触发 GC |
GC 根缓冲区 | 临时保存可能有问题的变量 |
手动 GC | 使用 gc_collect_cycles() |
查看状态 | 用 gc_status() |