1,zval详解(php5时期的)
/*这个是zval的实际结构,zval就是php中定义变量的容器,你申请一个变量就是创建一个zval
对于数组,数组本身是一个zval,数组中的每个值也是一个zval
*value; 是值或者是地址,内容是值还是地址,要看type的值是什么
*refcount__gc; 计数,用于垃圾回收,
$a = 'php'; //变量a的计数是1,不能被回收
$b = $a; //变量a的计数是2,不能被回收(php是写时复制,变量a,b都是同一个zval)
unset($b);//变量a的计数是1,不能被回收
unset($a);//变量a的计数是0,可以被回收,不代表马上就回收,具体要看gc是否触发
*type; 类型,类型有NULL,LONG,DOUBLE,BOOL,ARRAY,OBJECT,STRING,
IS_RESOURCE等类型
*is_ref__gc; 是否被引用(逻辑值),默认是0(没有引用),$b = &$a;变量a的is_ref__gc=1,被引用
*/
struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
typedef struct _zval_struct zval; //这个就是把这个结构起个别名
//这是个联合体
typedef union _zvalue_value {
long lval; //如果zval中的type是LONG,这个就是long的值
double dval; //如果zval中的type是double,这个就是double的值
struct { //如果string,是struct的地址,struct里是字符串的地址和长度
char *val;
int len;
} str;
HashTable *ht; //如果是array,就是hashtable的地址,因为数组就是hashtable实现的
zend_object_value obj; //如果是object,是对象的结构体
} zvalue_value;
2,zval详解(php7之后的,包括php7版本)
struct _zval_struct {
zend_value value; //这个就是下面的那个_zend_value名的联合体
union {
struct {
zend_uchar type, /*变量类型,null,false,true,long,double,string,array等
null,false,true他们只需要知道类型,不需要访问value*/
zend_uchar type_flags,/*变量类型掩码,不同的类型会有不同的几种属性。比如当前类型是
否支持引用计数、是否支持写时复制。主要在内存管理时会用*/
zend_uchar const_flags,//常量类型标记
zend_uchar reserved
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; //哈希表中解决哈希冲突时用到
uint32_t cache_slot;
uint32_t lineno;
uint32_t num_args;
uint32_t fe_pos;
uint32_t fe_iter_idx;
} u2; //一些辅助值
};
typedef struct _zval_struct zval; //把这个结构体起个别名zval
//zval的value部分,如果是long,double里面的内容的就是值,否则就是其他类型的地址
typedef union _zend_value {
zend_long lval; //存储的是值
double dval; //存储的是值
zend_refcounted *counted;
zend_string *str; //string字符串,结构的指针
zend_array *arr; //array数组,结构的指针
zend_object *obj; //object对象,结构的指针
zend_resource *res; //resource资源类型,结构的指针
zend_reference *ref; //引用类型,通过&$var_name定义的
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;
/*注意:php7的计数都是放到value里的,比如zend_string字符串结构体中,gc就是计数相关的数据
zend_array等结构中也有类似的
*/
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h;
size_t len;
char val[1];
};
3,php的写时复制
在写入的时候才真正复制一份,这样做的好处是减少内存的使用。
//以php5时期的zval结构讲解,php7的zval结构不同,原理一样的
$a = 'php'; //创建一变量(结构是一个zval,type是字符串,refcount__gc等于1)
$b = $a; //创建b变量,底层zval结构还是指向a的zval,这个zval的refcount__gc变成了2
$b = 'php7'; /*写时复制在这个时候发生(改变量b的值,但是变量a的值不变),创建了一个新的zval,值内容
是php7,原来那个a对应的zval内容不变,还是php,但是refcount__gc会减1,变成了1
*/
4,php的数组原理(php5的hashtable原理)
php的数组,在底层使用hashtable实现的
//hashtable结构
typedef struct _hashtable {
uint nTableSize; //hashtable表的大小,都是2的n次方,扩容都是2倍的扩,最小为8
uint nTableMask; //是一个掩码,用于快速计算索引的
uint nNumOfElements; //这个数组放入了多少元素,count($ar)返回的就是这个值
ulong nNextFreeElement; //下一个可用的数字索引,例如$ar[] = 'abc',索引就是这个确定的
Bucket *pInternalPointer;//一个指针,在使用current,next,key,end等函数就是这个这个记录位置
Bucket *pListHead; //链表的头元素地址
Bucket *pListTail; //链表的尾元素地址
Bucket **arBuckets; //bucket *类型的数组,bucket是实际存储数据的容器,下面那个结构就是
dtor_func_t pDestructor;
zend_bool persistent;
unsigned char nApplyCount;
zend_bool bApplyProtection;
} HashTable;
/*这个结构是hashtable的的主要结构,存储数据的容器,数组有多少个元素,就会有多少个Bucket
*对于数字型索引,直接使用h作为hash值,同时,arKey=NULL 且nKeyLength=0
*对于字符串索引,arKey保存字符串key, nKeyLength保存该key的长度,h则是key的hash值
*/
typedef struct bucket {
ulong h; //数字索引值或者字符串的hash值
uint nKeyLength; //key的长度
void *pData;
void *pDataPtr;
struct bucket *pListNext; //整个hash表的,下一个元素(保证数据顺序)
struct bucket *pListLast; //整个hash表的,前一个元素(保证数据顺序)
struct bucket *pNext; //相同slot的链表的,下一个元素(hash冲突的时候,寻找数据)
struct bucket *pLast; //相同slot的链表的,前一个元素(hash冲突的时候,寻找数据)
const char *arKey; //key值
} Bucket;
5,php的数组原理(php7的hashtable原理)
typedef struct _Bucket {
zval val; //值放到了zval中
zend_ulong h; //hash值
zend_string *key;//key值
} Bucket;
typedef struct _zend_array HashTable;
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar reserve)
} v;
uint32_t flags;
} u;
uint32_t nTableMask; //哈希值掩码,等于nTableSize的负值(nTableMask = ~nTableSize + 1)
Bucket *arData; //存储元素数组,指向第一个Bucket,数组中每个元素都是Bucket
uint32_t nNumUsed; //arData数组已经使用的数量,已用Bucket数,unset元素时,这个不变
uint32_t nNumOfElements; //哈希表已有元素数,unset元素时,这个会减1
uint32_t nTableSize; //哈希表总大小,为2的n次方,最小值为8
uint32_t nInternalPointer;//内部的指针,用于HashTable遍历
zend_long nNextFreeElement; //下一个可用的数值索引,如:arr[] = 1
dtor_func_t pDestructor; // 析构函数
}
6,hashtable扩容原理
当哈希表中存储的键值对超过负载因子阈值时,就需要进行扩容操作。负载因子是指哈希表中当前存储的键值对数量与哈希表总容量之间的比值,默认的负载因子大小为0.75。扩容的目的是减少hash冲突的概率,提高访问和更新效率。
7,哈希冲突的解决方法
链接法:php的hashtable用的是这种方法,如果遇到hash冲突的时候,通过链表把hash冲突的数据弄成一个链表,查找的时候,定位到所在的slot后,遍历这个链表。
开放寻址法:在存入的时候,通过公式 f(key,n)取hash,其中n可以认为是[0,1,2,3....],f(key,0)的hash值寻找slot位,如果没有数据,就使用这个位置,如果有数据,在通过f(key,1)取hash计算slot位,直到找到位置为止。查询的时候也是一样,通过f(key,0)的hash找到slot位,如果有数据,比较key是否相等,相等就是要找的数据,如果不相等,在通过f(key,1)的hash找slot位,如果找到的slot为空,就是没有找到这个key对应的值,可以结束寻找。
8,php的垃圾回收机制(gc)
php.ini配置里的zend.enable_gc是控制gc是否打开,默认打开;
函数gc_enable() 和 gc_disable()在运行时来打开和关闭垃圾回收机制;gc_collect_cycles()强制收集所有现存的垃圾循环周期
每个内存对象(就是zval)都分配一个计数器,当内存对象被变量引用时,计数器+1;
当变量引用撤掉后(执行unset()后),计数器-1;
当计数器=0,且没有引用时,表明内存对象没有被使用,该内存对象可以进行销毁。
垃圾回收的目的不用程序员手动管理内存,提高了编程的安全性和效率,避免了内存溢出的发生。
是如果遇到循环引用,就会出现内存泄漏的情况。
9,php怎么处理循环引用的问题?
循环引用的这种情况,是在php5.3以后才有的解决方法。
//循环引用的过程(对象,数组才会有循环引用的情况)
$ar = []; //会产生一个zval,类型是数组,计数(refcount__gc)是1
$ar['a'] = 'php'; //也会产生一个zval,类型是字符串,他的计数(refcount__gc)是1
$ar['b'] = &$ar; /*也会产生一个zval,是一个引用类型,计数(refcount__gc)是1,
由于引用了$ar,$ar的计数(refcount__gc)变成了2,zval引用(is_ref__gc)是1,
*/
unset($ar); /*会把$ar的计数(refcount__gc)减1,次数(refcount__gc)计数是1,垃圾回收不会回
收$ar
unset($ar)的本意是不用这个数组了,可以回收的,但是有循环引用,gc无法回收
他,这就是一个内存泄漏
*/
//----------------分割线------------------
//一个不存在循环引用的过程
$ar = []; //会产生一个zval,类型是数组,计数(refcount__gc)是1
$ar['a'] = 'php'; //也会产生一个zval,类型是字符串,他的计数(refcount__gc)是1
$ar['b'] = 123; //也会产生一个zval,计数(refcount__gc)是1,
unset($ar); //会把$ar的计数(refcount__gc)减1,计数变成了0,直接回收
解决方法就是增加了一个缓冲区(默认长度是10000),unset某个变量后,如果计数(refcount__gc)大于0(如果等于0,就可以直接回收,肯定是垃圾),那他可能就是存在循环引用的情况,就把它放入这个缓冲区(肯定也是需要排重的),如果这个缓冲区满了,就开始进行判断。判断方式是遍历取出缓冲区的每个元素,然后遍历每个元素,对每个元素做unset模拟操作,这个元素模拟操作后,在回来查看refcount_gc,如果变成了0,就是垃圾,可以回收,否则就不是,不能进行回收,且需要把模拟的unset给反向加回去。
//遇到循环引用,gc是怎么处理的
$ar = [];
$ar['a'] = 'php';
$ar['b'] = &$ar;
unset($ar); /*unset($ar)后,$ar对应的zval的refcount__gc=1,大于0,可能存在循环引用,需要加
入缓冲区
当缓冲区满了,取出$ar,然后遍历$ar的所有元素,模拟操作,
unset($ar['a']),unset($ar['b'])(这步就是断开引用关系,会使$ar的
refcount__gc减1),遍历完了后,在看$ar的refcount__gc已经变成0了,说明是循环引
用,可以进行回收了
如果最后发现refcount__gc任然大于0,就不是循环引用,把原来所有元素refcount__gc
的给还原回来
*/
------------------------------------------推荐阅读----------------------------------------------------------------
标签:__,面试题,struct,zend,zval,gc,PHP,ar,大全 From: https://blog.csdn.net/geegtb/article/details/136762530