分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 教程案例

PHP内核之旅-6.垃圾回收机制

发布时间:2023-09-06 02:02责任编辑:董明明关键词:PHP

回收PHP 内核之旅系列

PHP内核之旅-1.生命周期

PHP内核之旅-2.SAPI中的Cli

PHP内核之旅-3.变量

PHP内核之旅-4.字符串

PHP内核之旅-5.强大的数组

PHP内核之旅-6.垃圾回收机制

一、引用计数

只有使用引用计数的变量才需要回收。引用计数就是用来标记变量的引用次数的。

当有新的变量zval指向value时,计数器加1,当变量zval销毁时,计数器减一。当引用计数为0时,表示此value没有被任何变量指向,可以对value进行释放。

下面的例子说明引用计数的是如何变化的:

$x = array(); //array这个value被变量$x引用1次,refcount = 1$y = $x; //array这个value被变量$x,$y分别引用1次,refcount = 2$z = $y; //array这个value被变量$x,$y,$z分别引用1次,refcount = 3unset($y); //array这个value被变量$x,$z分别引用1次,refcount = 2,$y被销毁了,没有引用array这个value

使用引用计数的类型有以下几种:

string、array、object、resource、reference

下面的表格说明了只有type_flag为以下8种类型且IS_TYPE_REFOUNTED=true的变量才使用引用计数

 type_flagIS_TYPE_REFCOUNTED
1        simple types 
2stringtrue
3interned string 
4arraytrue
5immutable array 
6objecttrue
7resourcetrue
8referencetrue

1.正常回收场景:

a.自动回收

  在zval断开value的指向时,如果发现refcount=0则会直接释放value。

    断开value指向的情形:

    (1)修改变量时会断开原有value的指向

    (2)函数返回时会释放所有的局部变量

b.主动回收

  unset()函数

2.垃圾回收场景:

当因循环引用导致无法释放的变量称为垃圾,用垃圾回收器进行回收。

注意:

(1)如果一个变量value的refcount减一之后等于0,此value可以被释放掉,不属于垃圾。垃圾回收器不会处理。

(2)如果一个变量value的refcount减一之后还是大于0,此value被认为不能被释放掉,可能成为一个垃圾。

(3)垃圾回收器会将可能的垃圾收集起来,等达到一定数量后开始启动垃圾鉴定程序,把真正的垃圾释放掉。

(4)收集的时机是refount减少时。

(5)收集到的垃圾保存到一个buffer缓冲区中。

(6)垃圾只会出现在array、object类型中。

二、回收原理

1.垃圾是如何回收的

垃圾收集器收集的可能垃圾到达一定数量后,启动垃圾鉴定、回收程序。

2.垃圾鉴定

垃圾是由于成员引用自身导致的,那么就对value的refcount减一操作,如果value的refount变为了0,则表明其引用全部来自自身成员,value属于垃圾。

3.垃圾回收的步骤

步骤一:遍历垃圾回收器的buffer缓冲区,把value标为灰色,把value的成员的refount-1,标为白色。

步骤二:遍历垃圾回收器的buffer缓冲区,如果value的 refcount等于0,则认为是垃圾,标为白色;如果不等于0,则表示还有外部的引用,不是垃圾,将refcount+1还原回去,标为黑色。

步骤三:遍历垃圾回收器的buffer缓冲区,将value为非白色的节点从buffer中删除,最终buffer缓冲区中都是真正的垃圾。

步骤四:遍历垃圾回收器的buffer缓冲区,释放此value。

三、代码实现

1.垃圾管家

_zend_gc_globals 对垃圾进行管理,收集到的可能成为垃圾的value就保存在这个结构的buf中,称为垃圾缓存区。
文件路劲:\Zend\zend_gc.h
 1 typedef struct _zend_gc_globals { 2 ????zend_bool ????????gc_enabled; //是否启用GC 3 ????zend_bool ????????gc_active; //是否处于垃圾检查中 4 ????zend_bool ????????gc_full; //缓存区是否已满 5 ?6 ????gc_root_buffer ??*buf; //预分配的垃圾缓存区,用于保存可能成为垃圾的value 7 ????gc_root_buffer ???roots; //指向buf中最新加入的一个可能垃圾 8 ????gc_root_buffer ??*unused; //指向buf中没有使用的buffer 9 ????gc_root_buffer ??*first_unused; //指向第一个没有使用的buffer10 ????gc_root_buffer ??*last_unused; //指向最后一个没有使用的buffer11 12 ????gc_root_buffer ???to_free; //待释放的垃圾13 ????gc_root_buffer ??*next_to_free; //下指向下一个待释放的垃圾14 15 ????uint32_t gc_runs; //统计GC运行次数16 ????uint32_t collected; //统计已回收的垃圾数17 18 #if GC_BENCH19 ????uint32_t root_buf_length;20 ????uint32_t root_buf_peak;21 ????uint32_t zval_possible_root;22 ????uint32_t zval_buffered;23 ????uint32_t zval_remove_from_buffer;24 ????uint32_t zval_marked_grey;25 #endif26 27 ????gc_additional_buffer *additional_buffer;28 29 } zend_gc_globals;
_zend_gc_globals

2.垃圾管家初始化

(1)php.ini解析后调用gc_init()初始垃圾管家_zend_gc_globals 

文件路径:\Zend\zend_gc.c

1 ZEND_API void gc_init(void)2 {3 ????if (GC_G(buf) == NULL && GC_G(gc_enabled)) {4 ????????GC_G(buf) = (gc_root_buffer*) malloc(sizeof(gc_root_buffer) * GC_ROOT_BUFFER_MAX_ENTRIES);//GC_ROOT_BUFFER_MAX_ENTRIES=100015 ????????GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];6 ????????gc_reset();7 ????}8 }
gc_init

(2)gc_init()函数里面调用gc_reset()函数初始化变量

 1 ZEND_API void gc_reset(void) 2 { 3 ????GC_G(gc_runs) = 0; 4 ????GC_G(collected) = 0; 5 ????GC_G(gc_full) = 0; 6 ?7 ????GC_G(roots).next = &GC_G(roots); 8 ????GC_G(roots).prev = &GC_G(roots); 9 10 ????GC_G(to_free).next = &GC_G(to_free);11 ????GC_G(to_free).prev = &GC_G(to_free);12 13 ????GC_G(unused) = NULL;14 ????GC_G(first_unused) = NULL;15 ????GC_G(last_unused) = NULL;16 ????17 ????GC_G(additional_buffer) = NULL;18 }
gc_reset

3.判断是否需要收集

(1)在销毁一个变量时就会判断是否需要收集。调用i_zval_ptr_dtor()函数

文件路径:Zend\zend_variables.h

 1 static zend_always_inline void i_zval_ptr_dtor(zval *zval_ptr ZEND_FILE_LINE_DC) 2 { 3 ????if (Z_REFCOUNTED_P(zval_ptr)) {//type_flags & IS_TYPE_REFCOUNTED 4 ????????zend_refcounted *ref = Z_COUNTED_P(zval_ptr); 5 ????????if (!--GC_REFCOUNT(ref)) {//refcount - 1 之后等于0,则不是垃圾,正常回收 6 ????????????_zval_dtor_func(ref ZEND_FILE_LINE_RELAY_CC); 7 ????????} else {//如果refcount - 1 之后仍然大于0,垃圾管家进行收集 8 ????????????gc_check_possible_root(ref); 9 ????????}10 ????}11 }
i_zval_ptr_dtor

(2)如果refcount减一后,refcount等于0,则认为不是垃圾,释放此value

 1 //文件路径:\Zend\zend_variables.c 2 ZEND_API void ZEND_FASTCALL _zval_dtor_func(zend_refcounted *p ZEND_FILE_LINE_DC) 3 { 4 ????switch (GC_TYPE(p)) { 5 ????????case IS_STRING: 6 ????????case IS_CONSTANT: { 7 ????????????????zend_string *str = (zend_string*)p; 8 ????????????????CHECK_ZVAL_STRING_REL(str); 9 ????????????????zend_string_free(str);10 ????????????????break;11 ????????????}12 ????????case IS_ARRAY: {13 ????????????????zend_array *arr = (zend_array*)p;14 ????????????????zend_array_destroy(arr);15 ????????????????break;16 ????????????}17 ????????case IS_CONSTANT_AST: {18 ????????????????zend_ast_ref *ast = (zend_ast_ref*)p;19 20 ????????????????zend_ast_destroy_and_free(ast->ast);21 ????????????????efree_size(ast, sizeof(zend_ast_ref));22 ????????????????break;23 ????????????}24 ????????case IS_OBJECT: {25 ????????????????zend_object *obj = (zend_object*)p;26 27 ????????????????zend_objects_store_del(obj);28 ????????????????break;29 ????????????}30 ????????case IS_RESOURCE: {31 ????????????????zend_resource *res = (zend_resource*)p;32 33 ????????????????/* destroy resource */34 ????????????????zend_list_free(res);35 ????????????????break;36 ????????????}37 ????????case IS_REFERENCE: {38 ????????????????zend_reference *ref = (zend_reference*)p;39 40 ????????????????i_zval_ptr_dtor(&ref->val ZEND_FILE_LINE_RELAY_CC);41 ????????????????efree_size(ref, sizeof(zend_reference));42 ????????????????break;43 ????????????}44 ????????default:45 ????????????break;46 ????}47 }
_zval_dtor_func

(3)如果refcount减一后,refcount大于0,则认为value可能是垃圾,垃圾管家进行收集

 1 \\文件路径:\Zend\zend_gc.h 2 static zend_always_inline void gc_check_possible_root(zend_refcounted *ref) 3 { 4 ????if (GC_TYPE(ref) == IS_REFERENCE) { 5 ????????zval *zv = &((zend_reference*)ref)->val; 6 ?7 ????????if (!Z_REFCOUNTED_P(zv)) { ?8 ????????/* 9 ????????????Z_TYPE_FLAGS 与 IS_TYPE_REFCOUNTED 与运算后,不等于0,则会被释放掉10 ????????????Z_REFCOUNTED_P --> ((Z_TYPE_FLAGS(zval) & IS_TYPE_REFCOUNTED) != 0)11 ????????????Z_TYPE_FLAGS(zval) --> (zval).u1.v.type_flags12 ????????????IS_TYPE_REFCOUNTED -> 1<<2 (0100)13 ????????*/14 ????????????return;15 ????????}16 ????????ref = Z_COUNTED_P(zv); //Z_COUNTED_P --> (zval).value.counted ?GC头部17 ????}18 ????if (UNEXPECTED(GC_MAY_LEAK(ref))) {19 ????????gc_possible_root(ref); //垃圾管家收集可能的垃圾20 ????}21 }
gc_check_possible_root

 4.收集垃圾

 1 \\文件路径:\Zend\zend_gc.c 2 ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref) 3 { 4 ????gc_root_buffer *newRoot; 5 ?6 ????if (UNEXPECTED(CG(unclean_shutdown)) || UNEXPECTED(GC_G(gc_active))) { 7 ????????return; 8 ????} 9 10 ????ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT); // 只有数组和对象才会出现循环引用的产生的垃圾,所以只需要收集数组类型和对象类型的垃圾11 ????ZEND_ASSERT(EXPECTED(GC_REF_GET_COLOR(ref) == GC_BLACK)); // 只收集颜色为GC_BLACK的变量12 ????ZEND_ASSERT(!GC_ADDRESS(GC_INFO(ref)));13 14 ????GC_BENCH_INC(zval_possible_root);15 16 ????newRoot = GC_G(unused); //拿出unused指向的节点17 ????if (newRoot) { //如果拿出的节点是可用的,则将unused指向下一个节点18 ????????GC_G(unused) = newRoot->prev;19 ????} else if (GC_G(first_unused) != GC_G(last_unused)) {//如果unused没有可用的,且first_unused还没有推进到last_unused,则表示buf缓存区中还有可用的节点20 ????????newRoot = GC_G(first_unused); //拿出first_unused指向的节点21 ????????GC_G(first_unused)++; //first_unused指向下一个节点22 ????} else {//buf缓存区已满,启动垃圾鉴定、垃圾回收23 ????????if (!GC_G(gc_enabled)) { //如果未启用垃圾回收,则直接返回24 ????????????return;25 ????????}26 ????????GC_REFCOUNT(ref)++;27 ????????gc_collect_cycles();28 ????????GC_REFCOUNT(ref)--;29 ????????if (UNEXPECTED(GC_REFCOUNT(ref)) == 0) {30 ????????????zval_dtor_func(ref);31 ????????????return;32 ????????}33 ????????if (UNEXPECTED(GC_INFO(ref))) {34 ????????????return;35 ????????}36 ????????newRoot = GC_G(unused);37 ????????if (!newRoot) {38 #if ZEND_GC_DEBUG39 ????????????if (!GC_G(gc_full)) {40 ????????????????fprintf(stderr, "GC: no space to record new root candidate\n");41 ????????????????GC_G(gc_full) = 1;42 ????????????}43 #endif44 ????????????return;45 ????????}46 ????????GC_G(unused) = newRoot->prev;47 ????}48 49 ????GC_TRACE_SET_COLOR(ref, GC_PURPLE); //将插入的变量标为紫色,防止重复插入50 ????//将该节点在buf数组中的位置保存到了gc_info中,当后续value的refcount变为了0,51 ????//需要将其从buf中删除时可以知道该value保存在哪个gc_root_buffer中52 ????GC_INFO(ref) = (newRoot - GC_G(buf)) | GC_PURPLE; 53 ????newRoot->ref = ref;54 55 ????//插入roots链表头部56 ????newRoot->next = GC_G(roots).next;57 ????newRoot->prev = &GC_G(roots);58 ????GC_G(roots).next->prev = newRoot;59 ????GC_G(roots).next = newRoot;60 61 ????GC_BENCH_INC(zval_buffered);62 ????GC_BENCH_INC(root_buf_length);63 ????GC_BENCH_PEAK(root_buf_peak, root_buf_length);64 }
gc_possible_root

5.释放垃圾

 ?1 ZEND_API int zend_gc_collect_cycles(void) ?2 { ?3 ????int count = 0; ?4 ??5 ????if (GC_G(roots).next != &GC_G(roots)) { ?6 ????????gc_root_buffer *current, *next, *orig_next_to_free; ?7 ????????zend_refcounted *p; ?8 ????????gc_root_buffer to_free; ?9 ????????uint32_t gc_flags = 0; 10 ????????gc_additional_buffer *additional_buffer_snapshot; 11 #if ZEND_GC_DEBUG 12 ????????zend_bool orig_gc_full; 13 #endif 14 ?15 ????????if (GC_G(gc_active)) { 16 ????????????return 0; 17 ????????} 18 ?19 ????????GC_TRACE("Collecting cycles"); 20 ????????GC_G(gc_runs)++; 21 ????????GC_G(gc_active) = 1; 22 ?23 ????????GC_TRACE("Marking roots"); 24 ????????gc_mark_roots(); 25 ????????GC_TRACE("Scanning roots"); 26 ????????gc_scan_roots(); 27 ?28 #if ZEND_GC_DEBUG 29 ????????orig_gc_full = GC_G(gc_full); 30 ????????GC_G(gc_full) = 0; 31 #endif 32 ?33 ????????GC_TRACE("Collecting roots"); 34 ????????additional_buffer_snapshot = GC_G(additional_buffer); 35 ????????count = gc_collect_roots(&gc_flags); 36 #if ZEND_GC_DEBUG 37 ????????GC_G(gc_full) = orig_gc_full; 38 #endif 39 ????????GC_G(gc_active) = 0; 40 ?41 ????????if (GC_G(to_free).next == &GC_G(to_free)) { 42 ????????????/* nothing to free */ 43 ????????????GC_TRACE("Nothing to free"); 44 ????????????return 0; 45 ????????} 46 ?47 ????????/* Copy global to_free list into local list */ 48 ????????to_free.next = GC_G(to_free).next; 49 ????????to_free.prev = GC_G(to_free).prev; 50 ????????to_free.next->prev = &to_free; 51 ????????to_free.prev->next = &to_free; 52 ?53 ????????/* Free global list */ 54 ????????GC_G(to_free).next = &GC_G(to_free); 55 ????????GC_G(to_free).prev = &GC_G(to_free); 56 ?57 ????????orig_next_to_free = GC_G(next_to_free); 58 ?59 #if ZEND_GC_DEBUG 60 ????????orig_gc_full = GC_G(gc_full); 61 ????????GC_G(gc_full) = 0; 62 #endif 63 ?64 ????????if (gc_flags & GC_HAS_DESTRUCTORS) { 65 ????????????GC_TRACE("Calling destructors"); 66 ?67 ????????????/* Remember reference counters before calling destructors */ 68 ????????????current = to_free.next; 69 ????????????while (current != &to_free) { 70 ????????????????current->refcount = GC_REFCOUNT(current->ref); 71 ????????????????current = current->next; 72 ????????????} 73 ?74 ????????????/* Call destructors */ 75 ????????????current = to_free.next; 76 ????????????while (current != &to_free) { 77 ????????????????p = current->ref; 78 ????????????????GC_G(next_to_free) = current->next; 79 ????????????????if (GC_TYPE(p) == IS_OBJECT) { 80 ????????????????????zend_object *obj = (zend_object*)p; 81 ?82 ????????????????????if (!(GC_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { 83 ????????????????????????GC_TRACE_REF(obj, "calling destructor"); 84 ????????????????????????GC_FLAGS(obj) |= IS_OBJ_DESTRUCTOR_CALLED; 85 ????????????????????????if (obj->handlers->dtor_obj 86 ?????????????????????????&& (obj->handlers->dtor_obj != zend_objects_destroy_object 87 ??????????????????????????|| obj->ce->destructor)) { 88 ????????????????????????????GC_REFCOUNT(obj)++; 89 ????????????????????????????obj->handlers->dtor_obj(obj); 90 ????????????????????????????GC_REFCOUNT(obj)--; 91 ????????????????????????} 92 ????????????????????} 93 ????????????????} 94 ????????????????current = GC_G(next_to_free); 95 ????????????} 96 ?97 ????????????/* Remove values captured in destructors */ 98 ????????????current = to_free.next; 99 ????????????while (current != &to_free) {100 ????????????????GC_G(next_to_free) = current->next;101 ????????????????if (GC_REFCOUNT(current->ref) > current->refcount) {102 ????????????????????gc_remove_nested_data_from_buffer(current->ref, current);103 ????????????????}104 ????????????????current = GC_G(next_to_free);105 ????????????}106 ????????}107 108 ????????/* Destroy zvals */109 ????????GC_TRACE("Destroying zvals");110 ????????GC_G(gc_active) = 1;111 ????????current = to_free.next;112 ????????while (current != &to_free) {113 ????????????p = current->ref;114 ????????????GC_G(next_to_free) = current->next;115 ????????????GC_TRACE_REF(p, "destroying");116 ????????????if (GC_TYPE(p) == IS_OBJECT) {117 ????????????????zend_object *obj = (zend_object*)p;118 119 ????????????????EG(objects_store).object_buckets[obj->handle] = SET_OBJ_INVALID(obj);120 ????????????????GC_TYPE(obj) = IS_NULL;121 ????????????????if (!(GC_FLAGS(obj) & IS_OBJ_FREE_CALLED)) {122 ????????????????????GC_FLAGS(obj) |= IS_OBJ_FREE_CALLED;123 ????????????????????if (obj->handlers->free_obj) {124 ????????????????????????GC_REFCOUNT(obj)++;125 ????????????????????????obj->handlers->free_obj(obj);126 ????????????????????????GC_REFCOUNT(obj)--;127 ????????????????????}128 ????????????????}129 ????????????????SET_OBJ_BUCKET_NUMBER(EG(objects_store).object_buckets[obj->handle], EG(objects_store).free_list_head);130 ????????????????EG(objects_store).free_list_head = obj->handle;131 ????????????????p = current->ref = (zend_refcounted*)(((char*)obj) - obj->handlers->offset);132 ????????????} else if (GC_TYPE(p) == IS_ARRAY) {133 ????????????????zend_array *arr = (zend_array*)p;134 135 ????????????????GC_TYPE(arr) = IS_NULL;136 137 ????????????????/* GC may destroy arrays with rc>1. This is valid and safe. */138 ????????????????HT_ALLOW_COW_VIOLATION(arr);139 140 ????????????????zend_hash_destroy(arr);141 ????????????}142 ????????????current = GC_G(next_to_free);143 ????????}144 145 ????????/* Free objects */146 ????????current = to_free.next;147 ????????while (current != &to_free) {148 ????????????next = current->next;149 ????????????p = current->ref;150 ????????????if (EXPECTED(current >= GC_G(buf) && current < GC_G(buf) + GC_ROOT_BUFFER_MAX_ENTRIES)) {151 ????????????????current->prev = GC_G(unused);152 ????????????????GC_G(unused) = current;153 ????????????}154 ????????????efree(p);155 ????????????current = next;156 ????????}157 158 ????????while (GC_G(additional_buffer) != additional_buffer_snapshot) {159 ????????????gc_additional_buffer *next = GC_G(additional_buffer)->next;160 ????????????efree(GC_G(additional_buffer));161 ????????????GC_G(additional_buffer) = next;162 ????????}163 164 ????????GC_TRACE("Collection finished");165 ????????GC_G(collected) += count;166 ????????GC_G(next_to_free) = orig_next_to_free;167 #if ZEND_GC_DEBUG168 ????????GC_G(gc_full) = orig_gc_full;169 #endif170 ????????GC_G(gc_active) = 0;171 ????}172 173 ????return count;174 }
zend_gc_collect_cycles

PHP内核之旅-6.垃圾回收机制

原文地址:https://www.cnblogs.com/jackson0714/p/php6.html

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved