php7扩展库(php7的垃圾回收机制)
上次学习了PHP通过引用计数自动处理变量的使用,当某个zend_value引用计数变为0时, 当前zend_value就会被销毁,这次来学习第二个方面,自动垃圾回收机制。
这里引用《php7底层设计与源码实现》中的例子
$a = [];
$a[] = &$a;
unset($a);
这里数组$a的元素引用了$a自身,造成在unset之后,自身的指向没有消除,成为垃圾。
主要结构
# 存放收集的数据
# 这是个双向链表
typedef struct _gc_root_buffer {
zend_refcounted *ref;
struct _gc_root_buffer *next; /* double-linked list */
struct _gc_root_buffer *prev;
uint32_t refcount;
} gc_root_buffer;
# 垃圾收集器主要结构
typedef struct _zend_gc_globals {
zend_bool gc_enabled; #是否启用
zend_bool gc_active; #是否在运行
zend_bool gc_full;#是否满了
gc_root_buffer *buf;#缓冲区,申请10000个 /* preallocated arrays of buffers */
gc_root_buffer roots; /* list of possible roots of cycles */
gc_root_buffer *unused; /* list of unused buffers */
gc_root_buffer *first_unused; /* pointer to first unused buffer */
gc_root_buffer *last_unused; /* pointer to last unused buffer */
gc_root_buffer to_free; /* list to free */
gc_root_buffer *next_to_free;
uint32_t gc_runs;
uint32_t collected;
#if GC_BENCH
uint32_t root_buf_length;
uint32_t root_buf_peak;
uint32_t zval_possible_root;
uint32_t zval_buffered;
uint32_t zval_remove_from_buffer;
uint32_t zval_marked_grey;
#endif
gc_additional_buffer *additional_buffer;
} zend_gc_globals;
我们梳理下逻辑:
一个变量在unset之后,如果它指向的zend_value的refcount==0则直接销毁即可,如果refcount>0,那么尝试进行--refcount操作,如果等于0了,说明它没有其他被指向的,则可以销毁,如果refcount还是>0,则它可能是垃圾,这时候需要放入垃圾回收器中。
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_UNSET_CV_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *var = EX_VAR(opline->op1.var);
# 必须是存在refcount的类型如数组、对象
if (Z_REFCOUNTED_P(var)) {
zend_refcounted *garbage = Z_COUNTED_P(var);
ZVAL_UNDEF(var);
SAVE_OPLINE();
# 进行refcount - 1操作, =0可以销毁
if (!--GC_REFCOUNT(garbage)) {
zval_dtor_func(garbage);
} else {
# refcount>0 可能是垃圾,放入 gc_globals进行处理
gc_check_possible_root(garbage);
}
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
} else {
# 其他类型直接置为is_undef类型
ZVAL_UNDEF(var);
}
ZEND_VM_NEXT_OPCODE();
}
static zend_always_inline void gc_check_possible_root(zend_refcounted *ref)
{
if (GC_TYPE(ref) == IS_REFERENCE) {
zval *zv = &((zend_reference*)ref)->val;
if (!Z_REFCOUNTED_P(zv)) {
return;
}
ref = Z_COUNTED_P(zv);
}
# 是否存在内存泄露问题 没看明白
if (UNEXPECTED(GC_MAY_LEAK(ref))) {
gc_possible_root(ref);
}
}
# 收集程序
ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref)
{
gc_root_buffer *newRoot;
# 正在进行gc
if (UNEXPECTED(CG(unclean_shutdown)) || UNEXPECTED(GC_G(gc_active))) {
return;
}
# 得是数组、对象
ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT);
# 不能是black ,black意味着不是垃圾
ZEND_ASSERT(EXPECTED(GC_REF_GET_COLOR(ref) == GC_BLACK));
ZEND_ASSERT(!GC_ADDRESS(GC_INFO(ref)));
GC_BENCH_INC(zval_possible_root);
# 空闲缓冲区的头地址
newRoot = GC_G(unused);
if (newRoot) {
# 如果存在,设置pre指针
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)--;
if (UNEXPECTED(GC_REFCOUNT(ref)) == 0) {
zval_dtor_func(ref);
return;
}
if (UNEXPECTED(GC_INFO(ref))) {
return;
}
newRoot = GC_G(unused);
if (!newRoot) {
#if ZEND_GC_DEBUG
if (!GC_G(gc_full)) {
fprintf(stderr, "GC: no space to record new root candidate\n");
GC_G(gc_full) = 1;
}
#endif
return;
}
GC_G(unused) = newRoot->prev;
}
GC_TRACE_SET_COLOR(ref, GC_PURPLE);
GC_INFO(ref) = (newRoot - GC_G(buf)) | GC_PURPLE;
newRoot->ref = ref;
newRoot->next = GC_G(roots).next;
newRoot->prev = &GC_G(roots);
GC_G(roots).next->prev = newRoot;
GC_G(roots).next = newRoot;
GC_BENCH_INC(zval_buffered);
GC_BENCH_INC(root_buf_length);
GC_BENCH_PEAK(root_buf_peak, root_buf_length);
}
php7底层设计与源码实现
总结下来就是:
1)对roots环中每个元素进行深度优先遍历,将每个元素中gc_info为紫色的标记元素为灰色,且引用计数减1。
2)扫描roots环中gc_info为灰色的元素,如果发现其引用计数仍旧大于0,说明这个元素还在其他地方使用,那么将其颜色重新标记会黑色,并将其引用计数加1(在第一步有减1操作)。如果发现其引用计数为0,则将其标记为白色。该过程同样为深度优先遍历。
3)扫描roots环,将gc_info颜色为黑色的元素从roots移除。然后对roots中颜色为白色的元素进行深度优先遍历,将其引用计数加1(在第一步有减1操作),然后将roots链表移动到待释放的列表中(to_free)。
4)释放to_free列表的元素。
参考书籍:《php7底层设计与源码实现》
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com