引用计数的出现是为了提醒内核在适当的机会(变量未被引用、无用)时释放内存以达到减少消耗。
zval是php变量的内部结构,php 7 与php 5相比发生了很大的改变:
在php 5 ,_zval_struct定义在Zend/zend.h
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
zend_ast *ast;
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
而在php 7 ,_zval_struct定义在Zend/zend_types.h
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;
union {
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
uint32_t extra; /* not further specified */
} u2;
};
其中一个比较重要的点就是引用计数器zend_refcounted *counted;
和引用flagzend_reference *ref;
从zval转移到了value,这样做带来的好处就是更加自然,因为zval只是变量的名称、一个载体,并且zval的存储大小也缩减了,能够降低系统占用。
对于一个声明的变量,它由zval和value构成:其中zval是变量容器,可以理解为变量名;value是变量值容器,存储变量的值。
每个php变量存在一个叫"zval"的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference
set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是"refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope),那些主脚本(比如:通过浏览器请求的的脚本)和每个函数或者方法也都有作用域。
当一个变量被赋常量值时,就会生成一个zval变量容器。
引用计数用来记录当前有多少zval指向同一个zend_value。 当有新的zval指向这个value时,计数器加1;当zval销毁时,计数器减1。当引用计数为0时,表示此value已经没有被任何变量指向,这个时候就可以对value进行释放了。
gc就是引用计数,它的类型是zend_refcounted_h,
typedef struct _zend_refcounted_h {
uint32_t refcount; /* reference counter 32-bit */
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type,
zend_uchar flags, /* used for strings & objects */
uint16_t gc_info) /* keeps GC root number (or 0) and color */
} v;
uint32_t type_info;
} u;
} zend_refcounted_h;
struct _zend_refcounted {
zend_refcounted_h gc;
};
使用xdebug_debug_zval(string valName)
可以查看引用计数和引用flag
$a = date("Y-m-d");
$b = $a;
xdebug_debug_zval('a'); //a:(refcount=2, is_ref=0)string '2021-10-27' (length=10)
并不是所有的类型都会用到引用计数,没有具体value结构的类型是不会用到的,比如整
型、浮点型、布尔型(True和False)、NULL,它们的值直接通过zval保存,因此这些类型不会共用value,而
是深拷贝。它们的引用计数恒为0。
$a = True;
$b = $a;
xdebug_debug_zval('a'); //a:(refcount=0, is_ref=0)boolean true
$c = False;
$d = $a;
xdebug_debug_zval('c'); //c:(refcount=0, is_ref=0)boolean false
$e = 1;
$f = $e;
xdebug_debug_zval('e'); //e:(refcount=0, is_ref=0)int 1
$g = 1.1;
$h = $g;
xdebug_debug_zval('g'); //g:(refcount=0, is_ref=0)float 1.1
$i = NULL;
$j = $i;
xdebug_debug_zval('i'); //i:(refcount=0, is_ref=0)null
还有两种类型没有引用计数,内部字符串(Interned String)和不可变数组(Immutable Array)。传入一个固定的字符串就属于内部字符串,就像C语言的常量一样,它会在请求结束后自动销毁,因此不需要引用计数。判断是否是内部字符串的依据是zend_refcounted_h.u.v.flags
$a = 'hello world!';
$b = $a;
xdebug_debug_zval('a'); //a:(refcount=0, is_ref=0)string 'hello world!' (length=12)
总结在PHP中,只有五种类型支持引用计数:
名称 | 类型 |
---|---|
(非内部)字符串 | String |
(可变)数组 | Array |
引用 | Reference |
资源 | Resource |
对象 | Object |
使用unset()
手动回收后引用计数会自减
$a = date('Y-m-d');
$b = $a;
xdebug_debug_zval('a'); //a:(refcount=2, is_ref=0)string '2021-10-27' (length=10)
unset($b);
xdebug_debug_zval('a'); //a:(refcount=1, is_ref=0)string '2021-10-27' (length=10)
当引用计数归零时:php的gc机制就会回收它
欢迎在评论区留言,欢迎关注我的CSDN @Ho1aAs