浅谈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行将html' target='_blank'>全局变量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_BENCHGC_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- FREE_HASHTABLE(Z_ARRVAL(p- } else {zval_dtor( p- 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- 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变量
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP !
相关推荐:
浅谈PHP源码三十三:PHP5.3新增加的垃圾回收机制(Garbage Collection)基础
浅谈PHP源码三十二:PHP内存池中的emalloc/efree层与堆(heap)层
浅谈PHP源码二十九:关于接口的继承
以上就是浅谈PHP源码三十四:PHP5.3新增加的垃圾回收机制(Garbage Collection)的详细内容,PHP教程
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。
新闻热点
疑难解答