baiyan
全部视频:https://segmentfault.com/a/11...
垃圾回收器缓冲区元素的存储
- 在上一篇文章【PHP源码学习】2019-04-01 PHP垃圾回收1中,我们将所有疑似垃圾的元素都放到垃圾回收器缓冲区中,一直存下去,待存满垃圾回收器缓冲区10000个存储单元之后,垃圾回收算法就会启动,对缓冲区中的所有疑似垃圾进行标记与清除。垃圾回收算法需要对这个缓冲区进行扫描遍历,判定每一个存储单元中的内容是否为最终的垃圾。
- 回顾gc_possible_root()函数,我们将疑似垃圾存入了一个大小为10000的缓冲区中:
ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref){ ... 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)) { return; } GC_REFCOUNT(ref)++; gc_collect_cycles(); //真正启动垃圾回收算法 GC_REFCOUNT(ref)--; ... }}
- 由此可见,垃圾回收算法会在缓冲区存满的时候才会启动,这样会减少垃圾回收算法运行的频率,进而减小性能的损耗且提高效率(详见上一篇文章)
垃圾回收算法的运行
- 在此之前,我们已经将垃圾缓冲区内的所有元素均标记成了紫色GC_PURPLE
- 我们在gc_possible_root()函数中调用了gc_collect_cycles()函数,这个gc_collect_cycles是一个函数指针,实际指向的是zend_gc_collect_cycles()函数,最终会调用它:
ZEND_API int zend_gc_collect_cycles(void){ ... //遍历垃圾回收器链表,如果垃圾回收器链表中的元素是紫色,对其进行深度优先遍历并将refcount减1,并把它们标记为灰色GC_GREY gc_mark_roots(); //遍历垃圾回收器链表,如果垃圾回收器链表中的元素是灰色,那么判断每个疑似垃圾的元素的refcount是否>0,如果>0就不是垃圾,将其标记为黑色GC_BLACK并且将refcount+1恢复到原来的值;否则为垃圾,将其标记为白色GC_WHITE,它们是真正的垃圾,后面需要释放掉 gc_scan_roots(); ... //把垃圾回收器中为白色GC_WHITE的垃圾单独摘出来放到to_free垃圾链表中,并删除垃圾回收器中不是垃圾的元素 count = gc_collect_roots(&gc_flags); GC_G(gc_active) = 0; if (GC_G(to_free).next == &GC_G(to_free)) { /* 如果垃圾链表to_free为空,那么就没有需要释放的垃圾,直接返回 */ GC_TRACE("Nothing to free"); return 0; } /* 将全局变量中的垃圾链表拷贝到局部变量中 */ to_free.next = GC_G(to_free).next; to_free.prev = GC_G(to_free).prev; to_free.next->prev = &to_free; to_free.prev->next = &to_free; /* 释放全局的垃圾链表 */ GC_G(to_free).next = &GC_G(to_free); GC_G(to_free).prev = &GC_G(to_free); orig_next_to_free = GC_G(next_to_free); ... if (gc_flags & GC_HAS_DESTRUCTORS) { GC_TRACE("Calling destructors"); /* 在析构之前记录引用计数*/ current = to_free.next; while (current != &to_free) { current->refcount = GC_REFCOUNT(current->ref); current = current->next; } /* 析构对象*/ current = to_free.next; while (current != &to_free) { p = current->ref; GC_G(next_to_free) = current->next; if (GC_TYPE(p) == IS_OBJECT) { zend_object *obj = (zend_object*)p; if (!(GC_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { GC_TRACE_REF(obj, "calling destructor"); GC_FLAGS(obj) |= IS_OBJ_DESTRUCTOR_CALLED; if (obj->handlers->dtor_obj && (obj->handlers->dtor_obj != zend_objects_destroy_object || obj->ce->destructor)) { GC_REFCOUNT(obj)++; obj->handlers->dtor_obj(obj); GC_REFCOUNT(obj)--; } } } current = GC_G(next_to_free); } /* 销毁在对象析构过程中出现的值 */ current = to_free.next; while (current != &to_free) { GC_G(next_to_free) = current->next; if (GC_REFCOUNT(current->ref) > current->refcount) { gc_remove_nested_data_from_buffer(current->ref, current); } current = GC_G(next_to_free); } } /* 销毁zval */ GC_TRACE("Destroying zvals"); GC_G(gc_active) = 1; current = to_free.next; while (current != &to_free) { p = current->ref; GC_G(next_to_free) = current->next; GC_TRACE_REF(p, "destroying"); if (GC_TYPE(p) == IS_OBJECT) { //释放对象 zend_object *obj = (zend_object*)p; EG(objects_store).object_buckets[obj->handle] = SET_OBJ_INVALID(obj); GC_TYPE(obj) = IS_NULL; if (!(GC_FLAGS(obj) & IS_OBJ_FREE_CALLED)) { GC_FLAGS(obj) |= IS_OBJ_FREE_CALLED; if (obj->handlers->free_obj) { GC_REFCOUNT(obj)++; obj->handlers->free_obj(obj); GC_REFCOUNT(obj)--; } } SET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[obj->handle], EG(objects_store).free_list_head); EG(objects_store).free_list_head = obj->handle; p = current->ref = (zend_refcounted*)(((char*)obj) - obj->handlers->offset); } else if (GC_TYPE(p) == IS_ARRAY) { //释放数组 zend_array *arr = (zend_array*)p; GC_TYPE(arr) = IS_NULL; /* GC may destroy arrays with rc>1. This is valid and safe. */ HT_ALLOW_COW_VIOLATION(arr); zend_hash_destroy(arr); } current = GC_G(next_to_free); } /* 释放垃圾所占用内存空间 */ current = to_free.next; while (current != &to_free) { next = current->next; p = current->ref; if (EXPECTED(current >= GC_G(buf) && current < GC_G(buf) + GC_ROOT_BUFFER_MAX_ENTRIES)) { current->prev = GC_G(unused); GC_G(unused) = current; } efree(p); current = next; } while (GC_G(additional_buffer) != additional_buffer_snapshot) { gc_additional_buffer *next = GC_G(additional_buffer)->next; efree(GC_G(additional_buffer)); //通过efree()释放内存空间 GC_G(additional_buffer) = next; } /* 收尾 */ GC_TRACE("Collection finished"); GC_G(collected) += count; GC_G(next_to_free) = orig_next_to_free;#if ZEND_GC_DEBUG GC_G(gc_full) = orig_gc_full;#endif GC_G(gc_active) = 0; } return count;}
- 在第一步的gc_mark_roots()中,调用了gc_mark_grey(),标记为灰色:
static void gc_mark_roots(void){ gc_root_buffer *current = GC_G(roots).next; while (current != &GC_G(roots)) { if (GC_REF_GET_COLOR(current->ref) == GC_PURPLE) { gc_mark_grey(current->ref); } current = current->next; }}
- 在第二步的gc_scan_roots()中,调用了gc_scan(),进而又调用了gc_mark_black(),标记为黑色,而标记为白色没有调用函数:
static void gc_scan_roots(void){ gc_root_buffer *current = GC_G(roots).next; while (current != &GC_G(roots)) { gc_scan(current->ref); //内部还会调用gc_mark_black() current = current->next; }}
- 下面我们总结一下垃圾回收算法的执行流程:
- 调用函数gc_mark_roots();首先对垃圾回收器进行深度优先遍历,将refcount-1,并标记为灰色 - 调用函数gc_scan_roots();再次对垃圾回收器进行深度优先遍历,判断当前的refcount,如果大于0,就不是垃圾,标记为黑色,并且要将refcount+1恢复到原来的值;如果等于0,就是垃圾,标记为白色 - 调用函数gc_collect_roots(),将白色的垃圾从垃圾回收器中摘出来,放到垃圾链表中并等待回收;同时将垃圾回收器中不是垃圾的元素移除,以节省垃圾回收器空间 - 释放全局垃圾链表,拷贝到局部垃圾链表,提高效率 - 遍历局部垃圾链表 - 首先析构对象 - 销毁在对象析构过程中出现的值 - 销毁对象与数组的zval - 释放垃圾所占用内存空间 - 收尾工作
- 那么对其进行深度优先遍历并标记的代码都类似,举一个标记灰色的例子:
static void gc_mark_grey(zend_refcounted *ref){ HashTable *ht; Bucket *p, *end; zval *zv;tail_call: if (GC_REF_GET_COLOR(ref) != GC_GREY) { ht = NULL; GC_BENCH_INC(zval_marked_grey); GC_REF_SET_COLOR(ref, GC_GREY); if (GC_TYPE(ref) == IS_OBJECT) { zend_object_get_gc_t get_gc; zend_object *obj = (zend_object*)ref; if (EXPECTED(!(GC_FLAGS(ref) & IS_OBJ_FREE_CALLED) && (get_gc = obj->handlers->get_gc) != NULL)) { int n; zval *zv, *end; zval tmp; ZVAL_OBJ(&tmp, obj); ht = get_gc(&tmp, &zv, &n); end = zv + n; if (EXPECTED(!ht)) { if (!n) return; while (!Z_REFCOUNTED_P(--end)) { if (zv == end) return; } } while (zv != end) { if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); GC_REFCOUNT(ref)--; gc_mark_grey(ref); } zv++; } if (EXPECTED(!ht)) { ref = Z_COUNTED_P(zv); GC_REFCOUNT(ref)--; goto tail_call; } } else { return; } } else if (GC_TYPE(ref) == IS_ARRAY) { if (((zend_array*)ref) == &EG(symbol_table)) { GC_REF_SET_BLACK(ref); return; } else { ht = (zend_array*)ref; } } else if (GC_TYPE(ref) == IS_REFERENCE) { if (Z_REFCOUNTED(((zend_reference*)ref)->val)) { ref = Z_COUNTED(((zend_reference*)ref)->val); GC_REFCOUNT(ref)--; goto tail_call; } return; } else { return; } if (!ht->nNumUsed) return; p = ht->arData; end = p + ht->nNumUsed; while (1) { end--; zv = &end->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { break; } if (p == end) return; } while (p != end) { zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); GC_REFCOUNT(ref)--; gc_mark_grey(ref); } p++; } zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } ref = Z_COUNTED_P(zv); GC_REFCOUNT(ref)--; goto tail_call; }}
- 我们可以看到,该函数中用到了大量的递归调用。我们知道,垃圾回收就是用来解决循环引用的问题。循环引用的问题是自己引用自己,那么究竟在哪里才引用了自己,就需要进行深度优先遍历。举个例子,数组中的元素可能又是一个数组,里面的数组元素可能还是一个数组,间接引用了好多层,最后一层才引用了外层数组本身。那么,这种情况下,就需要不断地对其进行遍历并标记,直到找到最终最深的引用位置之前,所有中间的间接引用层都是垃圾,我们都需要对其进行标记并且清除。
- 比如之前讲过的循环引用示例:
- 这个例子很简单,只是嵌套了一层就引用回了自己。如果bucket中又是一个数组,它就会再次指向一个zend_array,而这个新zend_array又指向了一个bucket......等等,我们可以无限层嵌套下去,直到最后一层,才是深度优先遍历的终点。所以,我们在这个过程中所遍历的zend_array与bucket,全部都是垃圾,需要我们去标记与清除。
- 观察上图,我们可以注意到,在这个遍历过程中,所有的结构(zend_array或bucket等)的refcount都为1,这就证明了在垃圾回收算法中,为什么要先将refcount-1,然后判断是否大于0,如果小于等于0,那么他们就是垃圾,需要回收;如果大于0,则说明还有其他地方引用这个结构,那么他们不是垃圾,不需要回收。
- 我们最后总结一下颜色的变化:紫色 -> 灰色 -> 黑色或白色
- 紫色:代表该元素已经放入垃圾回收器- 灰色:代表该元素已经经过了refcount-1的操作- 黑色:不是垃圾,要将refcount+1还原- 白色:是垃圾,等待后续回收