PHP5的Zval容器
struct _zval_struct {
union {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} value; /* 变量的值 */
zend_uint refcount__gc; /* 引用次数 */
zend_uchar type; /* 变量当前的数据类型 */
zend_uchar is_ref__gc; /* 是否是属于引用集合 */
};
PHP7的Zval容器
struct _zval_struct {
union {
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;
} 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 var_flags;
uint32_t next; /* hash碰撞链 */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* 行号(AST,对象生成树槽点) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach位置 */
uint32_t fe_iter_idx; /* foreach迭代器索引 */
} u2;
};
PHP引用计数基本知识点
- 当一个变量被赋常量值时,就会生成一个zval变量容器。
- unset并非一定会释放内存,当有两个变量指向的时候,并非会释放变量占用的内存,只是refcount减1.
<PHP7
- php变量存在一个叫"zval"的变量容器中, zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set),通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是"refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope)。
>PHP7
- PHP变量容器"zval"中,zval_value 结构体中包含zend_refcounted、zend_reference分别替代了refcount,is_ref
内存管理机制
内存申请与释放设计
- 对于php的核心结构Hashtable来说,由于未知性,定义的时候不可能一次性分配足够多的内存块。所以初始化的时候只会分配一小块,等不够的时候在进行扩容,而Hashtable只扩容不减少。
- php并非简单的向os申请内存,而是会申请一大块内存,把其中一部分分给申请者,这样当再有逻辑来申请内存的时候,就不需要向os申请了,避免了频繁调用。当内存不够的时候才会再次申请。
- 当释放内存的时候,php并非会把内存还给os,而是把内存轨道自己维护的空闲内存列表,以便重复利用。
内存的分配做了两件事情
- 1.为变量名分配内存,存入符号表
- 2.为变量值分配内存
垃圾定义
- 判断有没有任何变量名指向变量容器zval, 如果没有则认为是垃圾,需要释放。
- 当变量容器zval中的refcount=0时,表示没有变量名指向该容器。
内存泄漏
环形引用
<?php
$a = ['one'];
$a[] = &$a;
xdebug_debug_zval('a');
/**
(refcount=2, is_ref=1),
array (size=2)
0 => (refcount=1, is_ref=0),string 'one' (length=3)
1 => (refcount=2, is_ref=1),
*/
处理垃圾内存
PHP5.3 && <PHP7
<?php
$a = ['one']; //--- zval_a(将$a对应的zval,命名为zval_a)
$a[] = &$a; //--- step1
unset($a); //--- step2
判断处理过程
- 1.如果一个zval的refcount增加,那么此zval还在使用,不属于垃圾
- 2.如果一个zval的refcount减少到0, 那么zval可以被释放掉,不属于垃圾
- 3.如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾
仅当因此出现第3种情况时进行如下操作
- zval容器放入缓冲区
- 直接将此zval节点放入一个节点(root)缓冲区(root buffer),并且将这些zval节点标记成紫色。
- 预减操作(子zval节点refcount减1)
- 当缓冲区被节点塞满的时候(或者进入垃圾回收周期),GC才开始开始对缓冲区中的zval节点进行垃圾判断。
- 垃圾判断算法以深度优先对节点所包含的zval进行减1操作,为了确保不会对同一个zval的refcount重复执行减1操作,一旦zval的refcount减1之后会将zval标记成灰色。在此期间,起初节点zval本身不做减1操作,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作。
- 垃圾判断
- 算法再次以深度优先判断每一个节点包含的zval的值,如果zval的refcount等于0,那么将其标记成白色(代表垃圾),如果zval的refcount大于0,那么将对此zval以及其包含的zval进行refcount加1操作,这个是对非垃圾的还原操作,同时将这些zval的颜色变成黑色(zval的默认颜色属性)。
- 释放垃圾
- 遍历zval节点,将垃圾判断过程中标记成白色的节点zval释放掉。
总结:
对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作,之后如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。
相关函数
- gc_enable() : 开启GC
- gc_disable() : 关闭GC
- gc_collect_cycles() : 在节点缓冲区未满的情况下强制执行垃圾分析算法
原文地址:https://www.cnblogs.com/one-villager/p/8865403.html
时间: 2024-11-09 00:13:38