前言
本文的目的不仅仅是总结PHP的垃圾回收机制,而是要发散思维,从垃圾回收机制中得到更多的细节。
什么是垃圾回收
垃圾回收机制广泛存在于各种编程语言中,由于每个变量被定义都会占用一部分内存空间,如果这些空间长期不被释放,系统的内存就会出现不足的情况,为了保证系统能在软件运行过程中有足够的内存使用,就需要这样一套回收机制,释放掉不需要使用的内存空间。
高级语言一般在自动实现了垃圾回收,不需要我们主动去控制。
PHP的垃圾回收实现
php5.3的zval
struct _zval_struct {
union {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
其中的refcount__gc
和is_ref__gc
负责垃圾回收机制的计数,这里被官方成为"引用计数".
php7的zval
struct _zval_struct {
union {
zend_long lval; /* long value */
double dval; /* double value */
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, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
} u2;
};
zend_refcounted用来实现垃圾回收。
从PHP7开始, 对于在zval的value字段中能保存下的值, 就不再对他们进行引用计数了, 而是在拷贝的时候直接赋值, 这样就省掉了大量的引用计数相关的操作, 这部分类型有:
- IS_LONG
- IS_DOUBLE
对于那种根本没有值, 只有类型的类型, 也不需要引用计数了:
- IS_NULL
- IS_FALSE
- IS_TRUE
而对于复杂类型, 一个size_t保存不下的, 那么我们就用value来保存一个指针, 这个指针指向这个具体的值, 引用计数也随之作用于这个值上, 而不在是作用于zval上了.
IS_ARRAY的结构如下
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar reserve)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};
其中zend_refcounted_h保存了垃圾回收相关的结构
typedef struct _zend_refcounted_h {
uint32_t refcount; /* reference counter 32-bit */
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type,
zend_uchar flags, /* used for strings & objects */
uint16_t gc_info) /* keeps GC root number (or 0) and color */
} v;
uint32_t type_info;
} u;
} zend_refcounted_h;
这样,PHP7的垃圾回收是通过refcount
来控制的,也是引用计数。
官方文档
官方文档的记录还是php5.2到5.3的垃圾回收机制,基本原理不变,但是控制的变量名称变了。
当引用计数变成0,系统认为变量没有用处,就会被垃圾回收。
参考资料
- https://www.laruence.com/2018/04/08/3170.html
- https://www.php.net/manual/zh/features.gc.refcounting-basics.php