『PHP内核』PHP 7 变量内存管理之引用计数(Reference Counting)

文章目录

  • 为什么会出现引用计数?
  • PHP 7 zval的变化
  • 引用计数(Reference Counting)
    • 引用计数的内部结构
    • 查看引用计数
    • 支持引用计数的类型
    • 减少引用计数

为什么会出现引用计数?

引用计数的出现是为了提醒内核在适当的机会(变量未被引用、无用)时释放内存以达到减少消耗。

PHP 7 zval的变化

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的存储大小也缩减了,能够降低系统占用。

引用计数(Reference Counting)

对于一个声明的变量,它由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

你可能感兴趣的:(#,PHP内核,php内核,引用计数,refcount,gc,c)