垃圾回收機制

1.正常回收場景:

a.自動回收php

  在zval斷開value的指向時,若是發現refcount=0則會直接釋放value。html

    斷開value指向的情形:數組

    (1)修改變量時會斷開原有value的指向緩存

    (2)函數返回時會釋放全部的局部變量ide

b.主動回收函數

  unset()函數ui

2.垃圾回收場景:

當因循環引用致使沒法釋放的變量稱爲垃圾,用垃圾回收器進行回收。spa

注意:3d

(1)若是一個變量value的refcount減一以後等於0,此value能夠被釋放掉,不屬於垃圾。垃圾回收器不會處理。code

(2)若是一個變量value的refcount減一以後仍是大於0,此value被認爲不能被釋放掉,可能成爲一個垃圾。

(3)垃圾回收器會將可能的垃圾收集起來,等達到必定數量後開始啓動垃圾鑑定程序,把真正的垃圾釋放掉。

(4)收集的時機是refount減小時。

(5)收集到的垃圾保存到一個buffer緩衝區中。

(6)垃圾只會出如今array、object類型中。

2、回收原理

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。

3、代碼實現

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; //指向第一個沒有使用的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 

文件路徑:\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=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()函數

文件路徑: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 }
複製代碼

(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 }
複製代碼

(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_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 \\文件路徑:\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_DEBUG
39             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 #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 }
相關文章
相關標籤/搜索