前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >对PHP变量的实现方式以及内存管理的梳理

对PHP变量的实现方式以及内存管理的梳理

作者头像
猿哥
发布2019-03-13 16:13:45
8700
发布2019-03-13 16:13:45
举报
文章被收录于专栏:Web技术布道师

变量

  • 局部变量 PHP中局部变量分配在zend_execute_data结构上,每次执行zend_op_array都会生成一个新的zend_execute_data 局部变量通过编译时确定的编号进行读写操作

静态变量 静态变量只会在编译时初始化,保存在zend_op_array->static_variables 这个哈希表中 静态变量通过哈希表保存,这就使得能像普通变量那样有一个固定的编号 编译时先判断zend_op_array->static_variables 是否已创建,然后将静态变量插入哈希表

代码语言:javascript
复制
    //zend_compile_static_var_common():
    if (!CG(active_op_array)->static_variables) {
    ALLOC_HASHTABLE(CG(active_op_array)->static_variables);
    zend_hash_init(CG(active_op_array)->static_variables, 8, NULL, ZVAL_P
    TR_DTOR, 0);
    }
    //插入静态变量
    zend_hash_update(CG(active_op_array)->static_variables, Z_STR(var_node.u.
    constant), value);

例如$count与 static_variables["count"]间的关系如图所示

  • 垃圾回收 一个是引用计数这个早期就有的基本机制,refcount减到0时,释放变量 这里同时也介绍下一个比较通用的写时复制机制,
代码语言:javascript
复制
 $a = 1;     
 $b = $a;     // 这里变量$a 与变量$b 持有的是同一个zend_val    
 $a = 2;     // 这个时候变量$a的值发生了改变,而显然,让$b的值也发生同样的改变是不符合预期的  所以这个时候就会发生zend_val的复制
 //另外一种情况 
 $a = 1; 
 $b = &$a; //当$b只有的是对$a的引用时,这两个变量始终共用同一个zend_val 
 $a = 2; //这时$b的值也为2   
  • 循环引用 引用计数机制有一个缺陷,就是碰到循环引用时,refcount无法减到0,导致变量无法释放,具体来说就是变量内部的成员引用了变量本身,比如数组中的某个元素指向了数组
代码语言:javascript
复制
 $a = [1];     
 $a[] = &$a;    
 unset($a);     

针对这种情况,php引入了垃圾回收器来处理 变量是否加入垃圾检查buffer并不是根据zval的类型判断的,而是与前面介绍的是否用到引用计数一样通过 zval.u1.type_flag 记录的,只有包含 IS_TYPE_COLLECTABLE 的变量才会被GC收集

目前垃圾只会出现在array、object两种类型中,只有这两种类型的变量会出现成员引用自身的情况 如果当变量的refcount减少后大于0,PHP并不会立即进行对这个变量进行垃圾鉴定,而是放入一个缓冲buffer中,等这个buffer满了以后(10000个值)再统一进行处理,加入buffer的是 变量zend_value的 zend_refcounted_h 一个变量只能加入一次buffer,为了防止重复加入,变量加入后会把 zend_refcounted_h.gc_info 置为 GC_PURPLE ,即标为紫色,下次refcount减少时 如果发现已经加入过了则不再重复插入。

垃圾缓存区是一个双向链表,等到缓存区满了以后则启动垃圾检查过程:遍历缓存区,再对当前变量的所有成员进行遍历,然后把成员的refcount减1(如果成员还包含子成员则也进行递归遍历,其实就是深度优先的遍历) 最后再检查当前变量的引用,如果减为了0则为垃圾 这个算法的原理很简单,垃圾是由于成员引用自身导致的,那么就对所有的成员减一遍引用,结果如果发现变量本身refcount变为了0则就表明其引用全部来自自身成员。

PHP对象在内存堆栈中的分配

对象在PHP里面和整型、浮点型一样,也是一种数据类,都是存储不同类型数据用的, 在运行的时候都要加载到内存中去用,那么对象在内存里面是怎么体现的呢?内存从逻辑上说大体上是分为4段,栈空间段、堆空间段、代码段、初始化静态段,程序里面不同的声明放在不同的内存段里面。

数据段(data segment)通常是指用来存放程序中已初始化且不为0的全局变量如:静态变量和常量

代码段(code segment / text segment)通常是指用来存放程序执行代码的一块内存区域,比如函数和方法

栈空间段是存储占用相同空间长度并且占用空间小的数据类型的地方,比如说整型1,10,100,1000,10000,100000 等等,在内存里面占用空间是等长的,都是64 位4 个字节。

(heap)数据长度不定长,而且占有空间很大的数据类型的数据放在堆内存里面的。

栈内存是可以直接存取的,而堆内存是 不可以直接存取的内存。对于我们的对象来数就是一种大的数据类型而且是占用空间不定长的类型,所以说对象是放在堆里面的,但对象名称是放在栈里面的,这样通过对象名称就可 以使用对象了。

  • PHP脚本运行的时候,那些变量被放到了栈内存,那些被保存到了堆内存?

在PHP5的Zend Engine的实现中,所有的值都是在堆上分配空间,并且通过引用计数和垃圾收集来管理. PHP5的Zend Engine主要使用指向zval结构的指针来操作值,在很多地方甚至通过zval的二级指针来操作.

而在PHP7的Zend Engine实现中,值是通过zval结构本身来操作(非指针). 新的zval结构直接被存放在VM[虚拟机?]的栈上,HashTable的桶里,以及属性槽里. 这样大大减少了在堆上分配和释放内存的操作,还避免了对简单值的引用计数和垃圾收集.

引用:

PHP对象在内存堆栈中的分配 - web21 - 博客园

《PHP7内核剖析》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-03-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP技术大全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 变量
  • PHP对象在内存堆栈中的分配
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档