发布于2026-07-20 阅读(0)
扫一扫,手机访问
PHP 的垃圾回收机制,一直是很多开发者感兴趣的话题。特别是从 PHP 5 升级到 PHP 7 之后,变量底层实现和回收策略都发生了不小的变化。今天,我们就来系统地梳理一下,这两个版本在变量存储和垃圾回收上到底有什么不同。
PHP 的变量是弱类型的,一个变量可以表示整数、浮点数、字符串,甚至是数组或对象。这种灵活性,其实得益于底层统一用了一个叫 zval 的结构体来承载。
在 PHP 5 时代,zval 的结构大致是这样的:
struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
}
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} zvalue_value;
简单来说,zval 里包含了一个联合体 zvalue_value 来存储实际的值,以及引用计数、类型标记和是否被引用的标记。引用计数和 is_ref 是直接放在 zval 结构体里的。
到了 PHP 7,zval 的结构被大幅重构,变得更紧凑、更高效:
struct _zval_struct {
union {
zend_long lval;
double dval;
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} value;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type,
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved)
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next;
uint32_t cache_slot;
uint32_t lineno;
uint32_t num_args;
uint32_t fe_pos;
uint32_t fe_iter_idx;
} u2;
};
可以看到,PHP 7 的 zval 不再直接存储引用计数了,而是把它放到了具体的 value 结构体中。同时,zval 本身不再处理写时复制(写时分离),而是通过一个专门的 zend_reference 结构体来表示引用关系。
在 PHP 5 中,变量赋值、函数传参等操作,都会直接操作 zval 里的 refcount__gc。当出现循环引用,比如两个数组互相引用,并且外部变量被 unset 之后,就会形成典型的“垃圾”。

而 PHP 7 的做法是,把引用计数放到具体的 value 类型中(比如 zend_string、zend_array),zval 本身不再负责计数。这带来的好处是,zval 可以更轻量地传递,写时复制(写时分离)的机制也被简化了。

理解了这些底层存储细节,我们才能真正理解 PHP 是如何做垃圾回收的。
首先,得明确什么才算“垃圾”。
在 PHP 7 中,垃圾收集器只处理两种类型:数组和对象。并且,只有满足以下条件时,才会被送入收集流程:
type_flag 标记为 IS_TYPE_COLLECTABLE。从 PHP 5.3 开始,垃圾回收算法就已经基本定型了。它的核心思路可以概括为以下几个步骤:

这套算法的巧妙之处在于,通过模拟减引用,可以快速识别出那些真正被循环引用“困住”的垃圾,而不会误伤正常的变量。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8