PHP源码阅读笔记三十三:PHP5.3新增加的垃圾回收机制(Garbage Collection)基础
PHP5.3中新增加了垃圾回收机制,据说很先进,据说引诱了我去看看其先进的实现。
官方说明文档请猛击Garbage Collection
中文版地址:http://docs.php.net/manual/zh/features.gc.php
【垃圾回收机制的嵌入方式】
zend_gc.h文件在zend.h的749行被引用:#include “zend_gc.h”
从而替换覆盖了在237行引用的zend_alloc.h文件中的ALLOC_ZVAL等宏
zend/zend_gc.h文件的202行开始
202 203 204 205 206 207 208 | /* The following macroses override macroses from zend_alloc.h */ #undef ALLOC_ZVAL #define ALLOC_ZVAL(z) \ do { \ (z) = (zval*)emalloc(sizeof(zval_gc_info)); \ GC_ZVAL_INIT(z); \ } while (0) |
ALLOC_ZVAL宏在zend_alloc.h中的定义是分配一个zval结构的内存空间。新的ALLOC_ZVAL宏分配了一个zval_gc_info结构的宏。zval_gc_info的结构如下:
zend/zend_gc.h文件的91行开始:
91 92 93 94 95 96 97 | typedef struct _zval_gc_info { zval z; union { gc_root_buffer *buffered; struct _zval_gc_info *next; } u; } zval_gc_info; |
zval_gc_info的第一个成员为zval结构,这就确保其和以zval变量分配的内存的开始对齐,从而在zval_gc_info类型指针的强制转换时,其可以作为zval使用。关于gc_root_buffer等将在后面的结构和实现时介绍,它定义的PHP垃圾回收机制的缓存结构。GC_ZVAL_INIT用来初始化替代了zval的zval_gc_info,它会把zval_gc_info中的成员u的buffered字段设置成NULL,此字段仅在将其放入垃圾回收缓冲区时才会有值,否则会一直是NULL。
由于PHP中所有的变量都是以zval变量的形式存在,这里以zval_gc_info替换zval,从而成功实现垃圾收集机制在原有系统中的集成。
这个有点面向对象中多态的感觉。
【垃圾回收机制的存储方式】
结点结构:
81 82 83 84 85 86 87 88 89 | typedef struct _gc_root_buffer { struct _gc_root_buffer *prev; /* double-linked list */ struct _gc_root_buffer *next; zend_object_handle handle; /* must be 0 for zval */ union { zval *pz; zend_object_handlers *handlers; } u; } gc_root_buffer; |
很明显(见注释,虽然PHP中的注释很少,但是有些纯粹是纠结的注释),这是一个双向链表。
在联合体中的pz变量很明显就是之前定义的多态的zval_gc_info结构,于是其在链表中的当前结点指针可以通过((zval_gc_info*)(pz))->u.buffered获取,不过在看其源码中有多处使用到这个调用方式,为何不另起一个宏呢?难道是怕宏太多,不是啊,PHP就是以宏多著称,比这个宏嵌套多的宏海了去了。不懂。另外handle等结构是特别针对对象变量的。
缓冲区是话在全局变量中的,和其它模块的全局变量一样,gc也有其自己的全局变量访问宏 GC_G(v),同样对于全局变量访问宏在是否ZTS下有不同的实现。
在zend_gc.h中定义的全局变量如下:
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | typedef struct _zend_gc_globals { zend_bool gc_enabled; /* 是否开启垃圾收集机制 */ zend_bool gc_active; /* 是否正在进行 */ 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) */ zval_gc_info *zval_to_free; /* 将要释放的zval变量的临时列表(temporaryt list of zvals to free) */ zval_gc_info *free_list; /* 临时变量,需要释放的列表开头 */ zval_gc_info *next_to_free; /* 临时变量,下一个将要释放的变量位置*/ zend_uint gc_runs; /* gc运行的次数统计 */ zend_uint collected; /* gc中垃圾的个数 */ // 省略... |
【垃圾回收机制中的颜色标记】
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | #define GC_COLOR 0x03 #define GC_BLACK 0x00 #define GC_WHITE 0x01 #define GC_GREY 0x02 #define GC_PURPLE 0x03 #define GC_ADDRESS(v) \ ((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR)) #define GC_SET_ADDRESS(v, a) \ (v) = ((gc_root_buffer*)((((zend_uintptr_t)(v)) & GC_COLOR) | ((zend_uintptr_t)(a)))) #define GC_GET_COLOR(v) \ (((zend_uintptr_t)(v)) & GC_COLOR) #define GC_SET_COLOR(v, c) \ (v) = ((gc_root_buffer*)((((zend_uintptr_t)(v)) & ~GC_COLOR) | (c))) #define GC_SET_BLACK(v) \ (v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) & ~GC_COLOR)) #define GC_SET_PURPLE(v) \ (v) = ((gc_root_buffer*)(((zend_uintptr_t)(v)) | GC_PURPLE)) |
在PHP的内存管理中我们也有看到类似的以最后位作为某种类型的标记方式。
这里以内存分配的最后两位作为整个结构的颜色标记。其中
白色表示垃圾
紫色表示已放入缓冲区
灰色表示已经进行了一次refcount的减一操作
黑色是默认颜色,正常
【zval定义的改变】
PHP3.0版本 在zend/zend.h文件中,其定义如下:
316 317 318 319 320 321 322 | struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; }; |
在php3.0之前的版本,如php5.2.9版本,在zend/zend.h文件中,其定义如下:
307 308 309 310 311 312 313 | struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount; zend_uchar type; /* active type */ zend_uchar is_ref; }; |
Pingback引用通告: Thinking In LAMP Blog » Blog Archive » PHP每月通讯(2010年12月)