(转)PHP zval内存回收机制和refcount_gc和is_ref_gc

出处 : http://blog.sina.com.cn/s/blog_75a2f94f0101gygh.html

对于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来实现。见下图【在引用时强制复制!】

zval内存回收机制和refcount_gc和is_ref_gc"
name="image_operate_25521387355284329" alt="PHP zval内存回收机制和refcount_gc和is_ref_gc"
src="http://s12.sinaimg.cn/mw690/0029z5oPgy6F5DXdCP11b&690" width="500"
height="391">

同样,下面的这段代码同样会在内核中产生歧义,所以需要强制复制!

$a = 1;

$b = &$a;

$c = $a;

zval内存回收机制和refcount_gc和is_ref_gc"
name="image_operate_10321387355292675" alt="PHP zval内存回收机制和refcount_gc和is_ref_gc"
src="http://s9.sinaimg.cn/mw690/0029z5oPgy6F5DYEQcw38&690" width="496"
height="390">

需要注意的是,在这两种情况下,$b都与原初的zval相关联,因为当复制发生时,内核还不知道第三个变量的名字。

(转)PHP zval内存回收机制和refcount_gc和is_ref_gc,布布扣,bubuko.com

时间: 2024-10-13 22:16:53

(转)PHP zval内存回收机制和refcount_gc和is_ref_gc的相关文章

php内存回收机制的学习

今天朋友去面试,回来问了一下怎么样,结果他说一脸懵逼,看来我们平时还是学习的太少了啊.于是比较好奇,果断问了一下都有哪些问题,朋友说第一个问题就是"描述PHP的垃圾回收机制",我当时听了也是一脸茫然,因为平时我们业务逻辑写的太多,很少去关注这些,但是没办法,既然有人问这个问题,看来还是很有必要了解一下的.于是马上搜了一下,网上资料文章很多,看了几篇后加上自己的一些理解记录一下. 首先看了一下官方手册,只有php5.3版本以后的才有了所谓的新的垃圾回收机制GC,那么以前是怎么干的呢?以前

关于仿照java的内存回收机制实现C++的自动内存回收的一点想法

java的内存回收机制是很高效的,对软件产生的额外影响很小.而在C++中的大多数智能指针都是采用的引用计数的策略实现,当计数到0时,将所指向的指针删除.这种智能指针当应用到比较大的对象或者动态内存分配的次数非常少时.对软件的性能不会有多大的影响,反而提高了对内存的使用效率.可是一旦使用动态内存分配的次数非常巨大的时候.不仅对内存的使用效率下降,软件的运行效率也会下降很多.这主要是因为,动态分配造成的存储碎片化使可用内存减少,cache命中率也会下降.对软件性能可能会造成几百倍的损失. 目前的想法

Android 操作系统的内存回收机制(转载)

http://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/index.html Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化, 使得其进程调度与资源管理与其他平台的 Linux 有明显的区别.主要包含下面几个层次: Application FrameworkApplication

Android 操作系统的内存回收机制

转自:http://android.jobbole.com/25169/ 简介:Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使应用程序关闭但不退出,并由操作系统进行进程的回收管理.本文在 Application Framework 与 Linux 内核两个层次上,以进程为粒度,对 Android 操作系统的进程资源回收机制进行了剖析.读者可以从本文获得对 Android 应用程序的生存周期的进一步理解

Android 操作系统的内存回收机制[转]

转自:http://www.ibm.com/developerworks/cn/opensource/os-cn-android-mmry-rcycl/ Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理与其他平台的 Linux 有明显的区别.主要包含下面几个层次: Application Framework Application Framewo

【转】Android 内存回收机制(默认回收与kernel回收)

Android APP 的运行环境 Android 是一款基于 Linux 内核,面向移动终端的操作系统.为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理与其他平台的 Linux 有明显的区别.主要包含下面几个层次: Application Framework Application Framework 将整个操作系统分隔成两个部分.对应用开发者而言,所有 APP 都是运行在 Application Framework 之上,而并不需要关心系统底层的

memcache的内存回收机制

memcache不会释放内存,而是重新利用. 在缓存的清除方面,memcache是不释放已分配内存.当已分配的内存所在的记录失效后,这段以往的内存空间,memcache只会重复利用. memcached的内存回收机制不是说你设置的key到了生命周期就自动从内存中清除的,这个时候必须有一个新的对象入驻请求这个大小的chunk或者 这个过期的对象被get的时候才会清除. 那当所有给memcache的内存都被占用了,这个时候,memcache有两个设置,要么报错,要么,就是用 LRU方法,把last

js内存回收机制

Javascript语言有自己的一套内存回收机制,一般情况下局部变量和对象使用完就会被系统自动回收,无需我们理会.但是碰到闭包的情况这些变量和对象是不会被回收的,对于普通的web站点,页面刷新或跳转这些内存也会被回收.如果是单页web站点,页面切换及数据请求都是通过ajax无刷新机制实现的,页面资源无法自动回收,时间长了会严重影响性能,造成内存泄漏甚至页面崩溃直接退出,这时候手动释放不用资源就非常必要了,包含删除dom.释放对象等想手动释放含有闭包的对象时,必须先将引用对象属性的事件删除,然后设

linux kernel内存回收机制

转:http://www.wowotech.net/linux_kenrel/233.html linux kernel内存回收机制 作者:itrocker 发布于:2015-11-12 20:37 分类:内存管理 无论计算机上有多少内存都是不够的,因而linux kernel需要回收一些很少使用的内存页面来保证系统持续有内存使用.页面回收的方式有页回写.页交换和页丢弃三种方式:如果一个很少使用的页的后备存储器是一个块设备(例如文件映射),则可以将内存直接同步到块设备,腾出的页面可以被重用:如果