垃圾回收是内存管理重要模块之一,但并不是全部的内存都是垃圾。比如一个定义一个函数的局部变量,在函数执行完之后这个变量就会自动地被撤回,因此这个变量所占用的内存已经被系统收回,自然就不是垃圾了。
那么垃圾的定义是什么呢?
垃圾主要是用来描述那些已经不用但却无法回收的内存。
在php内核中,变量的数据结构是:
struct _zval_struct {
/*Variable information */
zvalue_valuevalue; /* value */
zend_uintrefcount__gc;
zend_uchartype; /* active type */
zend_ucharis_ref__gc;
};
第一个value是存储变量的具体值。
Refcount_gc是该变量被引用的次数。
Type是变量的类型。
Is_ref_gc是表明这个变量是否是引用。
比如下面这段代码:
<?php
$a=123;
那么这个时候,变量在php内核中便是:
Value=123,
Refcount_gc=1;
Type=1
Is_ref_gc=0
当变量a被引用的时候,他的引用计数便会增加:
<?php
$a=123;
$b=&$a;
但是这样子,$a和$b还是可以被正常地销毁,因此这里的两个变量也不能算是垃圾。一个变量会成为垃圾,通常是在他被循环引用的时候。
比如:
<?php
$a=array();
$a[]=&a;
这个时候,使用老的回收算法是无法回收这样子的垃圾的。所以为了解决这种情况,在php5.3之后使用了新的算法:引用计数系统中的同步周期回收。
这个算法将所有被销毁后引用计数大于1的变量保存在一个数据结构中。当保存有10000个这样的变量的时候,php就会执行这个新的算法:模拟删除、模拟恢复、真的删除。
在zend_gc.c中,可以看到:
#define GC_ROOT_BUFFER_MAX_ENTRIES 10000
这个常量定义了当变量缓存超过多少个的时候,便执行新的回收算法。
在zend_gc.h中,可以看到保存引用计数大约1的变量的数据结构;
typedef struct _gc_root_buffer {
struct_gc_root_buffer *prev; /* double-linked list */
struct_gc_root_buffer *next;
zend_object_handle handle; /*must be 0 for zval */
union{
zval *pz;
constzend_object_handlers *handlers;
}u;
} gc_root_buffer;
在zend_gc.c中可以看到该结构被初始化:
ZEND_API void gc_init(TSRMLS_D)
{
if(GC_G(buf) == NULL && GC_G(gc_enabled)) {
GC_G(buf)= (gc_root_buffer*) malloc(sizeof(gc_root_buffer) *GC_ROOT_BUFFER_MAX_ENTRIES);
GC_G(last_unused)= &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
gc_reset(TSRMLS_C);
}
}
上面的代码是的功能是检查根缓存是否还没分配并且垃圾回收机制是否被开启,如果还没分配并且垃圾回收机制被开启的话,那么分配一段GC_ROOT_BUFFER_MAX_ENTRIES长的内存。
当一个变量被销毁的时候,php使用一个函数来处理这个销毁的过程。在这个销毁的过程中,如果是临时变量的话,那么就会调用zval_dtor,如果是一般变量的话,则会使用zval_ptr_dtor。
这两个函数都会尝试销毁这个变量,但是当引用计数不等于0的时候,则会调用GC_ZVAL_CHECK_POSSIBLE_ROOT(*zval_ptr);将该变量放到垃圾列表中。
在gc_zval_possible_root中可以看到这么一段代码:
取到垃圾列表中没有被使用的地址,如果该地址存在,则设置未使用的地址。如果垃圾列表已经满了(GC_G(first_unused) == GC_G(last_unused)),则启动垃圾回收机制(gc_collect_cycles)。
所以其大概流程是: