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() : 在节点缓冲区未满的情况下强制执行垃圾分析算法
PHP中的垃圾回收机制
原文地址:https://www.cnblogs.com/one-villager/p/8865403.html