zoukankan      html  css  js  c++  java
  • 垃圾回收机制

    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中,称为垃圾缓存区。
    文件路劲:endzend_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; //指向第一个没有使用的buffer
    10     gc_root_buffer   *last_unused; //指向最后一个没有使用的buffer
    11 
    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_BENCH
    19     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 #endif
    26 
    27     gc_additional_buffer *additional_buffer;
    28 
    29 } zend_gc_globals;
    复制代码

    2.垃圾管家初始化

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

    文件路径:endzend_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=10001
    5         GC_G(last_unused) = &GC_G(buf)[GC_ROOT_BUFFER_MAX_ENTRIES];
    6         gc_reset();
    7     }
    8 }
    复制代码

    (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 }
    复制代码

    3.判断是否需要收集

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

    文件路径:Zendzend_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 }
    复制代码

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

    复制代码
     1 //文件路径:endzend_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 }
    复制代码

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

    复制代码
     1 \文件路径:endzend_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_flags
    12             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 }
    复制代码

     4.收集垃圾

    复制代码
     1 \文件路径:endzend_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_DEBUG
    39             if (!GC_G(gc_full)) {
    40                 fprintf(stderr, "GC: no space to record new root candidate
    ");
    41                 GC_G(gc_full) = 1;
    42             }
    43 #endif
    44             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 }
    复制代码

    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_DEBUG
    168         GC_G(gc_full) = orig_gc_full;
    169 #endif
    170         GC_G(gc_active) = 0;
    171     }
    172 
    173     return count;
    174 }
  • 相关阅读:
    C#中的委托,匿名方法和Lambda表达式
    模式化窗口问题![window.dialogArguments]
    js动态改变HiddenField值,后台不能获取值的问题
    将DataTable进行分页并生成新的DataTable
    出错提示为:该行已经属于另一个表 的解决方法
    如何在服务器端获得showModalDialog传递的参数
    Synchronized和SyncRoot与 集合类 的同步
    Oracle中数据出现####的问题
    如何防止多次提交按钮造成重复提交
    Invoke and BeginInvoke
  • 原文地址:https://www.cnblogs.com/liliuguang/p/9261601.html
Copyright © 2011-2022 走看看