static ZEND_INI_MH(OnUpdateGCEnabled) /* {{{ */
{
OnUpdateBool(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
if (GC_G(gc_enabled)) {
gc_init(TSRMLS_C);
}
return SUCCESS;
}
/* }}} */
ZEND_INI_BEGIN()
ZEND_INI_ENTRY("error_reporting", NULL, ZEND_INI_ALL, OnUpdateErrorReporting)
STD_ZEND_INI_BOOLEAN("zend.enable_gc", "1", ZEND_INI_ALL, OnUpdateGCEnabled, gc_enabled, zend_gc_globals, gc_globals)
#ifdef ZEND_MULTIBYTE
STD_ZEND_INI_BOOLEAN("detect_unicode", "1", ZEND_INI_ALL, OnUpdateBool, detect_unicode, zend_compiler_globals, compiler_globals)
#endif
ZEND_INI_END()
zend.enable_gc对应的操作函数为ZEND_INI_MH(OnUpdateGCEnabled),如果开启了垃圾回收机制, 即GC_G(gc_enabled)为真,则会调用gc_init函数执行垃圾回收机制的初始化操作。 gc_init函数在zend/zend_gc.c 121行,此函数会判断是否开启垃圾回收机制, 如果开启,则初始化整个机制,即直接调用malloc给整个缓存列表分配10000个gc_root_buffer内存空间。 这里的10000是硬编码在代码中的,以宏GC_ROOT_BUFFER_MAX_ENTRIES存在,如果需要修改这个值,则需要修改源码,重新编译PHP。 gc_init函数在预分配内存后调用gc_reset函数重置整个机制用到的一些全局变量,如设置gc运行的次数统计(gc_runs)和gc中垃圾的个数(collected)为0, 设置双向链表头结点的上一个结点和下一个结点指向自己等。除了这种提的一些用于垃圾回收机制的全局变量,还有其它一些使用较多的变量,部分说明如下:
复制代码 代码如下:
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中垃圾的个数 */
// 省略...
}
当我们使用一个unset操作想清除这个变量所占的内存时(可能只是引用计数减一),会从当前符号的哈希表中删除变量名对应的项, 在所有的操作执行完后,并对从符号表中删除的项调用一个析构函数,临时变量会调用zval_dtor,一般的变量会调用zval_ptr_dtor。
当然我们无法在PHP的函数集中找到unset函数,因为它是一种语言结构。 其对应的中间代码为ZEND_UNSET,在Zend/zend_vm_execute.h文件中你可以找到与它相关的实现。
zval_ptr_dtor并不是一个函数,只是一个长得有点像函数的宏。 在Zend/zend_variables.h文件中,这个宏指向函数_zval_ptr_dtor。 在Zend/zend_execute_API.c 424行,函数相关代码如下:
复制代码 代码如下:
ZEND_API void _zval_ptr_dtor(zval **zval_ptr ZEND_FILE_LINE_DC) /* {{{ */
{
#if DEBUG_ZEND>=2
printf("Reducing refcount for %x (%x): %d->%d\n", *zval_ptr, zval_ptr, Z_REFCOUNT_PP(zval_ptr), Z_REFCOUNT_PP(zval_ptr) - 1);
#endif
Z_DELREF_PP(zval_ptr);
if (Z_REFCOUNT_PP(zval_ptr) == 0) {
TSRMLS_FETCH();
if (*zval_ptr != &EG(uninitialized_zval)) {
GC_REMOVE_ZVAL_FROM_BUFFER(*zval_ptr);
zval_dtor(*zval_ptr);
efree_rel(*zval_ptr);
}
} else {
TSRMLS_FETCH();
if (Z_REFCOUNT_PP(zval_ptr) == 1) {
Z_UNSET_ISREF_PP(zval_ptr);
}
GC_ZVAL_CHECK_POSSIBLE_ROOT(*zval_ptr);
}
}
/* }}} */
从代码我们可以很清晰的看出这个zval的析构过程,关于引用计数字段做了以下两个操作:
如果变量的引用计数为1,即减一后引用计数为0,直接清除变量。如果当前变量如果被缓存,则需要清除缓存如果变量的引用计数大于1,即减一后引用计数大于0,则将变量放入垃圾列表。如果变更存在引用,则去掉其引用。
将变量放入垃圾列表的操作是GC_ZVAL_CHECK_POSSIBLE_ROOT,这也是一个宏,其对应函数gc_zval_check_possible_root, 但是此函数仅对数组和对象执行垃圾回收操作。对于数组和对象变量,它会调用gc_zval_possible_root函数。
复制代码 代码如下: