只有使用引用计数的变量才需要回收。引用计数就是用来标记变量的引用次数的。
当有新的变量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 = 3
unset($y); //array这个value被变量$x,$z分别引用1次,refcount = 2,$y被销毁了,没有引用array这个value
使用引用计数的类型有以下几种:
string、array、object、resource、reference
下面的表格说明了只有type_flag为以下8种类型且IS_TYPE_REFOUNTED=true的变量才使用引用计数
type_flag | IS_TYPE_REFCOUNTED | |
---|---|---|
1 | simple types | |
2 | string | true |
3 | interned string | |
4 | array | true |
5 | immutable array | |
6 | object | true |
7 | resource | true |
8 | reference | true |
a.自动回收
在zval断开value的指向时,如果发现refcount=0则会直接释放value。
断开value指向的情形:
(1)修改变量时会断开原有value的指向
(2)函数返回时会释放所有的局部变量
b.主动回收
unset()函数
当因循环引用导致无法释放的变量称为垃圾,用垃圾回收器进行回收。
注意:
(1)如果一个变量value的refcount减一之后等于0,此value可以被释放掉,不属于垃圾。垃圾回收器不会处理。 (2)如果一个变量value的refcount减一之后还是大于0,此value被认为不能被释放掉,可能成为一个垃圾。 (3)垃圾回收器会将可能的垃圾收集起来,等达到一定数量后开始启动垃圾鉴定程序,把真正的垃圾释放掉。 (4)收集的时机是refount减少时。 (5)收集到的垃圾保存到一个buffer缓冲区中。 (6)垃圾只会出现在array、object类型中。
垃圾收集器收集的可能垃圾到达一定数量后,启动垃圾鉴定、回收程序。
垃圾是由于成员引用自身导致的,那么就对value的refcount减一操作,如果value的refount变为了0,则表明其引用全部来自自身成员,value属于垃圾。
步骤一:遍历垃圾回收器的buffer缓冲区,把value标为灰色,把value的成员的refount-1,标为白色。
步骤二:遍历垃圾回收器的buffer缓冲区,如果value的 refcount等于0,则认为是垃圾,标为白色;如果不等于0,则表示还有外部的引用,不是垃圾,将refcount+1还原回去,标为黑色。
步骤三:遍历垃圾回收器的buffer缓冲区,将value为非白色的节点从buffer中删除,最终buffer缓冲区中都是真正的垃圾。
步骤四:遍历垃圾回收器的buffer缓冲区,释放此value。
_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;
(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 }
(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 }
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 }
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 }
参考资料:
PHP7内核剖析