php7扩展库(php7的垃圾回收机制)

php7扩展库(php7的垃圾回收机制)(1)

上次学习了PHP通过引用计数自动处理变量的使用,当某个zend_value引用计数变为0时, 当前zend_value就会被销毁,这次来学习第二个方面,自动垃圾回收机制。

这里引用《php7底层设计与源码实现》中的例子

$a = []; $a[] = &$a; unset($a);

php7扩展库(php7的垃圾回收机制)(2)

php7扩展库(php7的垃圾回收机制)(3)

这里数组$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;

php7扩展库(php7的垃圾回收机制)(4)

我们梳理下逻辑:

一个变量在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扩展库(php7的垃圾回收机制)(5)

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

    分享
    投诉
    首页