← 回到文章

PHP源码阅读笔记三十四:PHP5.3新增加的垃圾回收机制(Garbage Collection)

PHP源码阅读笔记三十四:PHP5.3新增加的垃圾回收机制(Garbage Collection) 在之前的文章 PHP源码阅读笔记三十三:PHP5.3新增加的垃圾回收机制(Garbage Collection)基础 中有介绍了垃圾回收机制的一些基础知识。今天我们看看其初始化,添加到垃圾缓冲区和垃圾回收的过程。 官方说明文档请猛击Garbage Collection 中文版地址:http://docs.php.net/manual/zh/features.gc.php 【初始化】 在zend/zend_gc.c 121行有函数gc_init实现了gc的初始化,其代码如下:
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);
	}
}
第123行判断是否为空和是否开启了gc,如果都为真,则转124行 第124行是直接调用malloc分配了10000个gc_root_buffer内存。 第125行将全局变量last_unused设置为gc缓冲区的结束位置。 第126行重置整个垃圾收集机制,其代码从zend/zend_gc.c 88行开始,如下:
ZEND_API void gc_reset(TSRMLS_D)
{
	GC_G(gc_runs) = 0;
	GC_G(collected) = 0;

#if GC_BENCH
	GC_G(root_buf_length) = 0;
	GC_G(root_buf_peak) = 0;
	GC_G(zval_possible_root) = 0;
	GC_G(zobj_possible_root) = 0;
	GC_G(zval_buffered) = 0;
	GC_G(zobj_buffered) = 0;
	GC_G(zval_remove_from_buffer) = 0;
	GC_G(zobj_remove_from_buffer) = 0;
	GC_G(zval_marked_grey) = 0;
	GC_G(zobj_marked_grey) = 0;
#endif

	GC_G(roots).next = &GC_G(roots);
	GC_G(roots).prev = &GC_G(roots);

	if (GC_G(buf)) {
		GC_G(unused) = NULL;
		GC_G(first_unused) = GC_G(buf);

		GC_G(zval_to_free) = NULL;
	} else {
		GC_G(unused) = NULL;
		GC_G(first_unused) = NULL;
		GC_G(last_unused) = NULL;
	}
}
第90~91行 设置gc运行的次数统计(gc_runs)和gc中垃圾的个数(collected)为0。 第106~107行 设置双向链表头结点的上一个结点和下一个结点指向自己。 关于gc_enabled,默认情况下是开启的,可以在php.ini中配置。 其实现代码在zend/zend.c 93行 如下:
STD_ZEND_INI_BOOLEAN("zend.enable_gc",	"1",	ZEND_INI_ALL,	OnUpdateGCEnabled,   gc_enabled, zend_gc_globals,        gc_globals)
初始化调用在zend/zend.c 79 行
static ZEND_INI_MH(OnUpdateGCEnabled) /* {{{ */
{
	OnUpdateBool(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);

	if (GC_G(gc_enabled)) {
		gc_init(TSRMLS_C);
	}

	return SUCCESS;
}
【添加到垃圾缓冲区】 跟踪PHP的源码 zend/zend_execute_API.c 424行 [_zval_ptr_dtor] -> [GC_ZVAL_CHECK_POSSIBLE_ROOT()] -> [gc_zval_check_possible_root()] -> [gc_zval_possible_root()] 其中在gc_zval_check_possible_root()函数中,仅对数组和对象执行垃圾回收操作 gc_zval_possible_root函数的代码如下:
ZEND_API void gc_zval_possible_root(zval *zv TSRMLS_DC)
{
	if (UNEXPECTED(GC_G(free_list) != NULL &&
	               GC_ZVAL_ADDRESS(zv) != NULL &&
		           GC_ZVAL_GET_COLOR(zv) == GC_BLACK) &&
		           (GC_ZVAL_ADDRESS(zv) < GC_G(buf) ||
		            GC_ZVAL_ADDRESS(zv) >= GC_G(last_unused))) {
		/* The given zval is a garbage that is going to be deleted by
		 * currently running GC */
		return;
	}

	if (zv->type == IS_OBJECT) {
		GC_ZOBJ_CHECK_POSSIBLE_ROOT(zv);
		return;
	}

	GC_BENCH_INC(zval_possible_root);

	if (GC_ZVAL_GET_COLOR(zv) != GC_PURPLE) {
		GC_ZVAL_SET_PURPLE(zv);

		if (!GC_ZVAL_ADDRESS(zv)) {
			gc_root_buffer *newRoot = GC_G(unused);

			if (newRoot) {
				GC_G(unused) = newRoot->prev;
			} else if (GC_G(first_unused) != GC_G(last_unused)) {
				newRoot = GC_G(first_unused);
				GC_G(first_unused)++;
			} else {
				if (!GC_G(gc_enabled)) {
					GC_ZVAL_SET_BLACK(zv);
					return;
				}
				zv->refcount__gc++;
				gc_collect_cycles(TSRMLS_C);
				zv->refcount__gc--;
				newRoot = GC_G(unused);
				if (!newRoot) {
					return;
				}
				GC_ZVAL_SET_PURPLE(zv);
				GC_G(unused) = newRoot->prev;
			}

			newRoot->next = GC_G(roots).next;
			newRoot->prev = &GC_G(roots);
			GC_G(roots).next->prev = newRoot;
			GC_G(roots).next = newRoot;

			GC_ZVAL_SET_ADDRESS(zv, newRoot);

			newRoot->handle = 0;
			newRoot->u.pz = zv;

			GC_BENCH_INC(zval_buffered);
			GC_BENCH_INC(root_buf_length);
			GC_BENCH_PEAK(root_buf_peak, root_buf_length);
		}
	}
}
第132~140行 检查zval结点信息是否已经放入到结点缓冲区,如果已经放入到结点缓冲区,则直接返回,这样可以优化其性能 第142~145行 处理对象结点,直接返回,不再执行后面的操作 第149行 判断结点是否已经被标记为紫色,如果为紫色则不再添加到结点缓冲区,此处在于保证一个结点只执行一次添加到缓冲区的操作。 第150行 将结点的颜色标记为紫色,表示此结点已经添加到缓冲区,下次不用再做添加 第153~157行 找出新的结点的位置,如果缓冲区满了,则执行垃圾回收操作。 第176~184行 将新的结点添加到缓冲区所在的双向链表。 【垃圾回收过程】 在gc_zval_possible_root函数中,当缓冲区满时,程序调用gc_collect_cycles函数,执行垃圾回收操作。从zend/zend_gc.c文件615行开始,其实现代码如下:
ZEND_API int gc_collect_cycles(TSRMLS_D)
{
	int count = 0;

	if (GC_G(roots).next != &GC_G(roots)) {
		zval_gc_info *p, *q, *orig_free_list, *orig_next_to_free;

		if (GC_G(gc_active)) {
			return 0;
		}
		GC_G(gc_runs)++;
		GC_G(zval_to_free) = FREE_LIST_END;
		GC_G(gc_active) = 1;
		gc_mark_roots(TSRMLS_C);
		gc_scan_roots(TSRMLS_C);
		gc_collect_roots(TSRMLS_C);

		orig_free_list = GC_G(free_list);
		orig_next_to_free = GC_G(next_to_free);
		p = GC_G(free_list) = GC_G(zval_to_free);
		GC_G(zval_to_free) = NULL;
		GC_G(gc_active) = 0;

		/* First call destructors */
		while (p != FREE_LIST_END) {
			if (Z_TYPE(p->z) == IS_OBJECT) {
				if (EG(objects_store).object_buckets &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].valid &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount <= 0 &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.dtor &&
					!EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].destructor_called) {

					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].destructor_called = 1;
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount++;
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.dtor(EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.object, Z_OBJ_HANDLE(p->z) TSRMLS_CC);
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount--;
				}
			}
			count++;
			p = p->u.next;
		}

		/* Destroy zvals */
		p = GC_G(free_list);
		while (p != FREE_LIST_END) {
			GC_G(next_to_free) = p->u.next;
			if (Z_TYPE(p->z) == IS_OBJECT) {
				if (EG(objects_store).object_buckets &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].valid &&
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount <= 0) {
					EG(objects_store).object_buckets[Z_OBJ_HANDLE(p->z)].bucket.obj.refcount = 1;
					Z_TYPE(p->z) = IS_NULL;
					zend_objects_store_del_ref_by_handle_ex(Z_OBJ_HANDLE(p->z), Z_OBJ_HT(p->z) TSRMLS_CC);
				}
			} else if (Z_TYPE(p->z) == IS_ARRAY) {
				Z_TYPE(p->z) = IS_NULL;
				zend_hash_destroy(Z_ARRVAL(p->z));
				FREE_HASHTABLE(Z_ARRVAL(p->z));
			} else {
				zval_dtor(&p->z);
				Z_TYPE(p->z) = IS_NULL;
			}
			p = GC_G(next_to_free);
		}

		/* Free zvals */
		p = GC_G(free_list);
		while (p != FREE_LIST_END) {
			q = p->u.next;
			FREE_ZVAL_EX(&p->z);
			p = q;
		}
		GC_G(collected) += count;
		GC_G(free_list) = orig_free_list;
		GC_G(next_to_free) = orig_next_to_free;
	}

	return count;
}
第619行 判断缓冲区是否为空,如果为空则不会执行垃圾回收操作 第622行 判断垃圾回收操作是否正则进行,如果正在进行,则直接返回 第625~627行 将垃圾回收操作次数加1,初始化空闲列表,设置gc_active为1表示垃圾回归正在进行 第628行 此处为其官方文档中算法的步骤 B ,算法使用深度优先搜索查找所有可能的根,找到后将每个变量容器中的引用计数减1",为确保不会对同一个变量容器减两次"1",用灰色标记已减过1的。 第629行 这是算法的步骤 C ,算法再一次对每个根节点使用深度优先搜索,检查每个变量容器的引用计数。如果引用计数是 0 ,变量容器用白色来标记。如果引用次数大于0,则恢复在这个点上使用深度优先搜索而将引用计数减1的操作(即引用计数加1),然后将它们重新用黑色标记。 第630行 算法的最后一步 D ,算法遍历根缓冲区以从那里删除变量容器根(zval roots),同时,检查是否有在上一步中被白色标记的变量容器。每个被白色标记的变量容器都被清除。 在[gc_collect_cycles() -> gc_collect_roots() -> zval_collect_white() ]中我们可以看到,对于白色标记的结点会被添加到全局变量zval_to_free列表中。此列表在后面的操作中有用到。 第632~633行 将全局变量free_list和next_to_free存放在相对应当的临时变量中,在最后会恢复到此时的状态。 第634~635行 初始化需要清除的列表,清空将要清空的zval列表并且将垃圾收集的操作状态为不激活状态。 第639~655行 第一次调用析构函数,并统计清除的变量个数 第657~678行 清除变量 第682~686行 释放内存 第687~689行 处理垃圾个数统计,恢复free_list和next_to_free变量
最后更新于 2010-11-26 09:07:25