PHP zval内存回收机制和refcount_gc和is_ref_gc

对于PHP这种须要同时处理多个请求的程序来讲,申请和释放内存的时候应该慎之又慎,一不当心便会酿成大错。另外一方面,除了要安全的申请和释放内存外,还应该作到内存的最小化使用,由于它可能要处理每秒钟数以千计的请求,为了提升系统总体的性能,每一次操做都应该只使用最少的内存,对于没必要要的相同数据的复制则应该能免则免。咱们来看下面这段PHP代码:php

$a = "hello";html

$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很聪明!url

前面说过,PHP变量的名称和值在内核中是保存在两个不一样的地方的,值是经过一个与名字毫无关系的zval结构来保存,而这个变量的名字a则保存在符号表里,二者之间经过指针联系着。在咱们上面的例子里,$a是一个字符串,咱们经过zend_hash_add把它添加到符号表里,而后又把它赋值给$b,二者拥有相同的内容!若是二者指向彻底相同的内容,咱们有什么优化措施吗?spa


如今咱们检查$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结构了!orm


这个时候当咱们再用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来实现。见下图【在引用时强制复制!】

PHP <wbr>zval内存回收机制和refcount_gc和is_ref_gc

一样,下面的这段代码一样会在内核中产生歧义,因此须要强制复制!

$a = 1;

$b = &$a;

$c = $a;

PHP <wbr>zval内存回收机制和refcount_gc和is_ref_gc

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

相关文章
相关标签/搜索