本文目录导读:

PHP的垃圾回收(Garbage Collection,GC)机制主要针对Zend引擎的内存管理,它通过引用计数(Reference Counting)和周期自动检测(Cycle Collecting)两种方式协同工作,以下是其核心运作原理:
基础内存管理 – 引用计数
PHP变量存在一个zval结构中,每个zval包含一个refcount(引用计数)字段。
- 赋值:当变量被赋值给另一个变量时,
refcount加1。 - 函数调用:将变量作为参数传递时,新创建的变量会共享同一个
zval,引用计数增加。 - 销毁:当变量离开作用域(如函数结束、
unset())时,引用计数减1。 - 回收:当
refcount降至0时,Zend引擎立即回收该变量占用的内存。
例子:
$a = new stdClass(); // refcount = 1 $b = $a; // refcount = 2 unset($a); // refcount = 1 unset($b); // refcount = 0 → 立即回收
循环引用问题 – 周期检测
引用计数无法处理循环引用,当两个或多个对象相互引用时,引用计数永远不会降为0,导致内存泄漏。
class Node {
public $child;
}
$a = new Node();
$b = new Node();
$a->child = $b; // $b的refcount = 2(自身+$a->child)
$b->child = $a; // $a的refcount = 2
unset($a); // $a的refcount变为1,$b的refcount仍为2
unset($b); // $b的refcount变为1,$a的refcount仍为1
// 两个对象的refcount均>0,不会被引用计数回收
周期垃圾回收器(Cycle Collector)
PHP引入了一个专门的周期垃圾回收器来解决此问题,其核心是可能垃圾根缓冲区,运作分为几个阶段:
a)标记可疑根
当变量的refcount减少到一个非0值(即减少但没有归零)时,该变量会被加入可能垃圾根缓冲区,因为这种情况表明可能存在循环引用。
b)GC触发
- 主动触发:通过调用
gc_collect_cycles()手动启动。 - 自动触发:当缓冲区中的可疑根数量达到阈值(默认10000)时,GC自动运行。
- PHP 7.3+引入了增量GC(默认关闭),可以在请求中分步执行,避免长时间卡顿。
c)收集与清除
- 计算有效引用数:GC遍历所有可疑根,暂时减少每个节点的引用计数(记为
depth),并递归遍历所有子节点。 - 判断是否为垃圾:如果某个节点的
depth减为0(表示只有循环引用自身),则标记为垃圾。 - 释放:删除被标记为垃圾的节点及其循环引用链。
简化流程:
- 缓冲区装满(或手动触发)→ 遍历所有可疑根,模拟删除自身引用 → 若引用变为0,则为垃圾 → 真正释放。
PHP版本差异与优化
| 版本 | 关键变化 |
|---|---|
| PHP 5.3 | 引入周期回收器(默认开启) |
| PHP 7.0 | 引入改进的zval结构,引用计数更轻量 |
| PHP 7.3 | 增加增量GC(zend.enable_gc=0可关闭,增量模式需zend.gc_incremental) |
| PHP 8.0+ | 进一步优化内存布局,GC性能提升 |
如何观察与调优
- 查看当前垃圾:
gc_status()(PHP 8.1+)或gc_collect_cycles()的返回值。 - 关闭GC(不推荐):在
php.ini设置zend.enable_gc = Off。 - 触发阈值调整:
gc_enabled()返回当前状态,不可直接改阈值,但可通过代码控制,如在大量循环引用操作后手动gc_collect_cycles()。 - 减少循环引用:使用弱引用(
WeakReference,PHP 7.4+)或手动解除环形依赖。
PHP的GC = 引用计数(立即回收) + 周期回收器(清理循环引用),前者处理常规泄漏,后者在处理复杂对象图时额外启动,确保没有无用的循环结构长期占用内存,理解GC有助于处理长周期CLI脚本、高并发服务中的内存泄漏问题。