PHP zval内存回收机制和refcount_gc和is_ref_gc
2019独角兽企业重金招聘Python工程师标准>>>
对于PHP这种需要同时处理多个请求的程序来说,申请和释放内存的时候应该慎之又慎,一不小心便会酿成大错。另一方面,除了要安全的申请和释放内存外,还应该做到内存的最小化使用,因为它可能要处理每秒钟数以千计的请求,为了提高系统整体的性能,每一次操作都应该只使用最少的内存,对于不必要的相同数据的复制则应该能免则免。我们来看下面这段PHP代码:
$a = "hello";
$b = $a;
unset($a);
第一条语句执行后,PHP创建了$a这个变量,并为它申请了12B的内存来存放"hello world"这个字符串(最后加个NULL字符,你懂的)。紧接着把$a赋给了$b,并释放掉$a;
对于PHP来说,如果每一次变量赋值都执行一次内存复制的话,那需要额外申请12B的内存来存放这个重复的数据,当然为了复制内存,还需要cpu执行某些计算,这当然会加重cpu的负载。当第三句执行后,$a被释放了,我们刚才的设想突然变的这么滑稽,这次赋值显得好多余哦。如果早就知道$a不用了,那我们直接让$b用$a的内存不就行了,还赋值干嘛?如果你觉得12B没什么,那设想下如果$a是个10M的文件内容,或者20M,是不是我们的计算机资源消耗的有点冤枉呢?
别担心,PHP很聪明!
前面说过,PHP变量的名称和值在内核中是保存在两个不同的地方的,值是通过一个与名字毫无关系的zval结构来保存,而这个变量的名字a则保存在符号表里,两者之间通过指针联系着。在我们上面的例子里,$a是一个字符串,我们通过zend_hash_add把它添加到符号表里,然后又把它赋值给$b,两者拥有相同的内容!如果两者指向完全相同的内容,我们有什么优化措施吗?
现在我们检查$a和$b两个变量,他们的值指向了"Hello NowaMagic!"这个字符串在内存中的位置。但是在第三行:unset($a);这条语句释放了$a。在这种情况下,unset函数并不知道$a的值同时被$b用着,所以如果它直接释放内存,则会导致$b的值也被清空了,从而导致逻辑错误,甚至可能会导致系统崩溃。
呵呵,其实你心里明白,PHP不会让上述问题发生的!回顾一下zval的四个成员value、type、is_ref__gc、refcount__gc,我们对value和type已经很熟了,现在则是后两个成员发挥威力的时候了,这里我们主要讲解refcount__gc这个成员。当一个变量被第一次创建的时候,它对应的zval结构体的refcount__gc成员的值会被初始化为1,理由很简单,因为只有这个变量自己在用它。但是当你把这个变量赋值给别的变量时,refcount__gc属性便会加1变成2,因为现在有两个变量在用这个zval结构了!
这个时候当我们再用unset删除$a的时候,它删除符号表里的$a的信息,然后清理它的值部分,这时它发现$a的值对应的zval结构的refcount值是2,也就是有另外一个变量在一起用着这个zval,所以unset只需把这个zval的refcount减去1就行了!
引用计数绝对是节省内存的一个超棒的模式!但是当我们修改$b的值,而且还需要继续使用$a时,该怎么办呢?
$a =1;
$b = $a;
$b += 5;
从代码逻辑来看,我们希望语句执行后$a仍然是1,而$b则需要变成6。我们知道在第二句完成后内核通过让$a和$b共享一个zval结构来达到节省内存的目的,但是现在第三句来了,这时$b的改变应该怎样在内核中实现呢?
答案非常简单,内核首先查看refcount__gc属性,如果它大于1则为这个变化的变量从原zval结构中复制出一份新的专属与$b的zval来,并改变其值。现在$b变量拥有了自己的zval,并且可以自由的修改它的值了。
Change on Write 写时复制
如果用户在PHP脚本中显式的让一个变量引用另一个变量时,我们的内核是如何处理的呢?
$a =1;
$b =&$a;
$b += 5;
作为一个标准的PHP程序猿,我们都知道$a的值也变成6了。当我们更改$b的值时,内核发现$b是$a的一个用户端引用,也就是所它可以直接改变$b对应的zval的值,而无需再为它生成一个新的不同与$a的zval。因为他知道$a和$b都想得到这次变化!
但是内核是怎么知道这一切的呢?简单的讲,它是通过zval的is_ref__gc成员来获取这些信息的。这个成员只有两个值,就像开关的开与关一样。它的这两个状态代表着它是否是一个用户在PHP语言中定义的引用。在第一条语句($a = 1;)执行完毕后,$a对应的zval的refcount__gc等于1,is_ref__gc等于0;。 当第二条语句执行后($b = &$a;),refcount__gc属性向往常一样增长为2,而且is_ref__gc属性也同时变为了1!
最后,在执行第三条语句的时候,内核再次检查$b的zval以确定是否需要复制出一份新的zval结构来,这次不需要复制.
这一次,尽管它的refcount等于2,但是因为它的is_ref等于1,所以也不会被复制。内核会直接的修改这个zval的值。
Separation Anxiety
我们已经了解了php语言中变量的复制和引用的一些事,但是如果复制和引用这两个事件被组合起来使用了该怎么办呢?看下面这段代码:
$a = 1;
$b = $a;
$c = &$a;
这里我们可以看到,$a,$b,$c这三个变量现在共用一个zval结构,有两个属于change-on-write组合($a,$c),有两个属于copy-on-write组合($a,$b),我们的is_ref__gc和refcount__gc该怎样工作,才能正确的处理好这段复杂的关系呢?
The answer is: 不可能!在这种情况下,变量的值必须分离成两份完全独立的存在!$a与$c共用一个zval,$b自己用一个zval,尽管他们拥有同样的值,但是必须至少通过两个zval来实现。见下图【在引用时强制复制!】
同样,下面的这段代码同样会在内核中产生歧义,所以需要强制复制!
$a = 1;
$b = &$a;
$c = $a;
需要注意的是,在这两种情况下,$b都与原初的zval相关联,因为当复制发生时,内核还不知道第三个变量的名字。
转载于:https://my.oschina.net/u/196016/blog/263063
PHP zval内存回收机制和refcount_gc和is_ref_gc相关推荐
- 内存淘汰算法_「承」Redis 原理篇——Redis 的内存回收机制
前言 关于 Redis 的"起承转合",我前面已经用五个篇章的长度作了一个 Redis 基础篇--"起"篇的详细阐述,相信大家无论之前有没有接触过 Redis, ...
- cocos2d-x初探学习笔记(13)--内存回收机制
小满(bill man)个人原创,欢迎转载,转载请注明地址,小满(bill man)的专栏地址http://blog.csdn.net/bill_man 之前提到过cocos2d-x的内存回收机制,但 ...
- Redis的内存回收机制和数据过期淘汰策略
本文来说下Redis的内存回收机制和数据过期淘汰策略 文章目录 概述 为什么需要内存回收 过期删除策略 定时删除 惰性删除 定期删除 删除策略比对 过期删除策略原理 redisDb结构体定义 expi ...
- ESXi 内存回收机制
上一篇介绍了ESXi内存分配原理,本篇重点讲一下超分情况下,出现内存竞争时,VMkernel如何进行内存回收的. 如上图所示,当内存超分时,就可能出现虚拟机内存的竞争,VMkernel在处理竞争的时候 ...
- 【Qt教程】1.5 - Qt5内存回收机制-对象树、窗口坐标系
1.Qt内存回收机制 - 对象树 (做了解,懂就可以,示例看视频) 当创建的对象在堆区时,如果指定的父亲是 QObject派生下来的类 或者 QObject子类派生下来的类,可以不用管理释放的操作,对 ...
- Java技术专题之JVM逻辑内存回收机制研究图解版
一.引言 JVM虚拟机内存回收机曾迷惑了不少人,文本从JVM实现机制的角度揭示JVM内存回收的原理和机制. 一.Java平台逻辑架构 二.JVM物理结构 通过从JVM物理结构图我们可以看到: 1.JV ...
- Java进阶3. 内存回收机制
Java进阶3. 内存回收机制 20131029 前言: 学过C++的都知道,C++中内存需要程序员自己维护.说道这里,很多开发的同学就感觉很痛苦,当他转向Java的时候,就会说你看Java多好啊,程 ...
- 《Windows 8 权威指南》——2.5 Windows 8 Metro应用内存回收机制
本节书摘来自异步社区<Windows 8 权威指南>一书中的第2章,第2.5节,作者:胡耀文 , 尹成 , 李贝贝 , 卢磊 , 曹珍著,更多章节内容可以访问云栖社区"异步社区& ...
- JAVA的内存回收机制(快速入门版)
java内存回收机制 内存回收,是JVM中垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制 引用 : java中什么是引用? Person xiaoi =new ...
最新文章
- 学习Java知识应该注意哪些基础原则
- 【优秀作业】蚁群优化算法
- 在 Virt-manager 图形界面中使用桥接网络
- settings.xml 文件配置
- 关于U打包代码必须放到Editor目录下的问题
- linux 高性能服务排查方式
- binding.filter(filter) will trigger OData request to backend
- 微软认真聆听了开源 .NET 开发社区的炮轰: 通过CLI 支持 Hot Reload 功能
- 【期望】守卫挑战(金牌导航 期望-9)
- Java高级工程师必看系列,已拿到offer
- 微课|Python程序设计开发宝典(5.1.2节):嵌套函数定义2
- c语言程序的生命周期(编写、预处理、编译、汇编、链接、执行)
- cxgrid 行合并单元格_Excel合并单元格无法筛选和排序,你必须掌握这3个小技巧...
- 430.扁平化多级双向链表
- 美团2021届秋季校园招聘笔试真题解析:小美的仓库整理
- 怎样用计算机算出别人的出生日期,Excel根据出生日期计算年龄的步骤
- 自然辩证法与计算机科学与技术,自然辩证法与计算机科学技术.pdf
- 什么是计算机?计算机的硬件系统组成有哪些?
- Android-Hybrid-问题收集Android客户端无法拦截Vue路由的问题
- 高等数学笔记:极限的性质总结
热门文章
- KVM虚拟化存储管理
- iptables为什么需要增加loopback回环的规则
- 敏捷开发第二阶段个人(二)
- iOS开发 BLE 蓝牙4.0
- Asp.net实现直接在浏览器预览Word、Excel、PDF、Txt文件(附源码)
- CLLocation的distanceFromLocation方法计算地图上距离 (汇总)
- Linux MySQl 5.7.17 MySQL ERROR 1366(HY000):Incorrect string value 解决方法
- 页面布局 - flex弹性布局
- 启动脚本gameserver
- [官版翻译]OpenStack centos版安装(三)