php5.3 垃圾回收机制

来源:互联网 发布:php 7 soap 编辑:程序博客网 时间:2024/04/29 21:56

注意垃圾回收 的根缓冲区大小默认是1万(这里面包括了资源类型变量,猜测) 曾经有一个任务 在申请了大约10000个数据库资源后 则会卡住 应该是这个原因

注意拷贝分离发生在函数调用里面第一次使用的时候

一直以来,reference counting memory mechanisms 引用计数机制都存在这个问题,
直到2007年,David F. Bacon和V.T. Rajan 写了一篇论文”Concurrent Cycle Collection in Reference Counted Systems“,解决了这个问题。

于是Derick Rethans以及某些人一起,按照这篇论文,在PHP 5.3版本源代码里面解决了circular reference导致内存泄露的问题。

下面简单讲解一下这套算法:
首先,介绍一下背景知识:
如果zval容器的refcount增加了,说明有变量(符号)正在使用它,所以它肯定不是垃圾。
如果zval容器的refcount减少到0了,这个容器会被删除掉,空间被释放出来。
综上所述,只有在refcount减少到一个非零值的时候,才有可能存在垃圾回收的问题。

如果上面讲的知识您不太明白,那么请去复习一下关于垃圾回收的第一篇文章:php垃圾回收机制之变量的处理。

其次,在垃圾回收过程中,怎样来分辨哪些zval容器是需要回收的垃圾呢?
办法就是:让数组所有元素的zval容器的refcount减一。如果最终数组指向的zval的refcount值为0,那么此数组就是垃圾。正常情况下,最终的refcount应该为1。
挺难理解的吧,看图:

PHP垃圾回收机制 之 如何进行垃圾回收

花荣的说明:图中的a符号,我也不知道是怎样来的。。。。。。反正它不是原来的数组a了。
就假设它是另外一个新的变量好了,它指向array(0),因此array(0)的zval的refcount从1变成了2。
另外,所有zval的默认颜色都是黑色。

如果每当refcount减少的时候,都去完成一次垃圾回收过程,未免效率不高。
此算法准备了一个root buffer(也就是一个缓冲区),当一个zval容器的refcount减少到一个非零值的时候,
就把这个zval扔到root buffer里面,然后标记此zval为粉色。
同时还要保证,每个zval只会被扔进来一次~~。
使用了颜色之后,确实方便,只扔黑色的zval进来就可以了。
只有当root buffer被充满的时候(大概需要一万个zval容器才能充满它),
垃圾回收才开始启动。如上图中的A步骤所示。

在上图中的B步骤里,垃圾回收开始启动,
对于root buffer中的每一个zval,
都要使用深度优先的搜索算法(depth-first search),寻找它的所有元素的zval(在这个例子里面,就是数组的所有元素),
并且把元素的zval的refcount减一,然后标记被减一的zval为灰色。

在C步骤里面,依然是对于root buffer中的每一个zval,
都使用深度优先的搜索算法,寻找它的所有元素的zval。
如果元素的zval的refcount是0,就把这个zval标记为白色(在上图中以蓝色表示)。
如果元素的zval的refcount大于0,就让它以及它的所有子元素的zval的refcount加一,恢复原值,标记为黑色。这一步使用的仍然是深度优先算法。

步骤D,清空root buffer。同时把所有标记为白色的zval删除掉,释放空间。

PS:不知道我讲明白没有,反正当时我是看了好久才明白过来。
在此感谢 http://blog.csdn.net/phpkernel/archive/2010/07/14/5734743.aspx 此文作者。他从源代码的角度对GC进行了分析。有一定的可取之处。

结合原来的数组a,我们再来分析一下这个过程。
为了方便,我们把数组的zval命名为zval_array_a。把数组元素0的zval命名为zval_array_0。
unset($a)之后,
数组a指向的zval的refcount减一,值为1,于是它就被扔到了root buffer中,被标记为粉色。
这就是Step A。 Step A里面的符号a,仍然假设它是一个其它变量好了。。与原来的数组a没有任何关系。
假设这个时候,root buffer满了,垃圾回收开始启动。

进入步骤B。对root buffer中的每个zval进行遍历,
假设遍历到zval_array_a了。
使用深度优先算法,寻找zval_array_a的所有元素的zval,
找到了二个zval,一个是zval_array_0,另一个是zval_array_a。
zval_array_0的refcount是2, zval_array_a的refcount是1。
对其refcount进行减一,
zval_array_0的refcount变为1, zval_array_a的refcount变为0。
最后把zval_array_a和zval_array_0标记为灰色。步骤B完成。

进入步骤C,对root buffer中的每个zval进行遍历,
假设遍历到zval_array_a了。
使用深度优先算法,寻找zval_array_a的所有元素的zval,
找到了二个zval,一个是zval_array_0,另一个是zval_array_a。
元素zval_array_a的refcount为0,标记它为白色(图上的蓝色部分),
元素zval_array_0的refcount大于0,
标记为黑色,并且让它的refcount加一。
开始采用深度优先算法,寻找zval_array_0的子元素,如果能找到就给他们搞成黑色,再把他们的refcount加一。没找到就算了。
步骤C结束。

进入步骤D,root buffer被清空。同时,白色的zval—-zval_array_a被删除掉。
垃圾回收完成。

对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作,之后如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。

默认情况下,php 5.3中的垃圾回收是被启用的。可以在php.ini中对其进行设置:zend.enable_gc。
关闭GC之后,php仍然会把zval扔到root buffer中,但不会对其进行分析和处理,
如果root buffer满了,新的zval将不会再进入root buffer中。

The reason why possible roots are recorded even if the mechanism has been disable is because it’s faster to record possible roots, than to have to check whether the mechanism is turned on every time a possible root could be found。

之所以在GC关闭的时候,还会把zval扔到root buffer中,是因为这种操作速度很快,
比每次refcount-1的时候都去检测GC是否开启还要快一些。

由于垃圾回收的整个过程还是比较消耗时间的,如果我们的部分程序对时间很敏感,
那么有必要在程序中实时地关闭垃圾回收,PHP的开发者也想到了这一点:
除了在php.ini中对其设置,我们还可以在PHP代码里面使用gc_enable()和gc_disable()来开启/关闭垃圾回收。
通过gc_collect_cycles()函数,可以强制进行一次垃圾回收,不管root buffer有没有满。

在gc_disable()之前,最好先去调用一下gc_collect_cycles(),
把可回收的垃圾都回收一下。

在这篇文章里面,我们讲了garbage collection 垃圾回收是怎样运作的,以及在PHP中如何控制垃圾回收,
在下一篇文章里面,我们会进行一些评测Benchmark,来看一下GC的性能如何。


一直以来,reference counting memory mechanisms 引用计数机制都存在这个问题,
直到2007年,David F. Bacon和V.T. Rajan 写了一篇论文”Concurrent Cycle Collection in Reference Counted Systems“,解决了这个问题。

于是Derick Rethans以及某些人一起,按照这篇论文,在PHP 5.3版本源代码里面解决了circular reference导致内存泄露的问题。

下面简单讲解一下这套算法:
首先,介绍一下背景知识:
如果zval容器的refcount增加了,说明有变量(符号)正在使用它,所以它肯定不是垃圾。
如果zval容器的refcount减少到0了,这个容器会被删除掉,空间被释放出来。
综上所述,只有在refcount减少到一个非零值的时候,才有可能存在垃圾回收的问题。

如果上面讲的知识您不太明白,那么请去复习一下关于垃圾回收的第一篇文章:php垃圾回收机制之变量的处理。

其次,在垃圾回收过程中,怎样来分辨哪些zval容器是需要回收的垃圾呢?
办法就是:让数组所有元素的zval容器的refcount减一。如果最终数组指向的zval的refcount值为0,那么此数组就是垃圾。正常情况下,最终的refcount应该为1。
挺难理解的吧,看图:

PHP垃圾回收机制 之 如何进行垃圾回收

花荣的说明:图中的a符号,我也不知道是怎样来的。。。。。。反正它不是原来的数组a了。
就假设它是另外一个新的变量好了,它指向array(0),因此array(0)的zval的refcount从1变成了2。
另外,所有zval的默认颜色都是黑色。

如果每当refcount减少的时候,都去完成一次垃圾回收过程,未免效率不高。
此算法准备了一个root buffer(也就是一个缓冲区),当一个zval容器的refcount减少到一个非零值的时候,
就把这个zval扔到root buffer里面,然后标记此zval为粉色。
同时还要保证,每个zval只会被扔进来一次~~。
使用了颜色之后,确实方便,只扔黑色的zval进来就可以了。
只有当root buffer被充满的时候(大概需要一万个zval容器才能充满它),
垃圾回收才开始启动。如上图中的A步骤所示。

在上图中的B步骤里,垃圾回收开始启动,
对于root buffer中的每一个zval,
都要使用深度优先的搜索算法(depth-first search),寻找它的所有元素的zval(在这个例子里面,就是数组的所有元素),
并且把元素的zval的refcount减一,然后标记被减一的zval为灰色。

在C步骤里面,依然是对于root buffer中的每一个zval,
都使用深度优先的搜索算法,寻找它的所有元素的zval。
如果元素的zval的refcount是0,就把这个zval标记为白色(在上图中以蓝色表示)。
如果元素的zval的refcount大于0,就让它以及它的所有子元素的zval的refcount加一,恢复原值,标记为黑色。这一步使用的仍然是深度优先算法。

步骤D,清空root buffer。同时把所有标记为白色的zval删除掉,释放空间。

PS:不知道我讲明白没有,反正当时我是看了好久才明白过来。
在此感谢 http://blog.csdn.net/phpkernel/archive/2010/07/14/5734743.aspx 此文作者。他从源代码的角度对GC进行了分析。有一定的可取之处。

结合原来的数组a,我们再来分析一下这个过程。
为了方便,我们把数组的zval命名为zval_array_a。把数组元素0的zval命名为zval_array_0。
unset($a)之后,
数组a指向的zval的refcount减一,值为1,于是它就被扔到了root buffer中,被标记为粉色。
这就是Step A。 Step A里面的符号a,仍然假设它是一个其它变量好了。。与原来的数组a没有任何关系。
假设这个时候,root buffer满了,垃圾回收开始启动。

进入步骤B。对root buffer中的每个zval进行遍历,
假设遍历到zval_array_a了。
使用深度优先算法,寻找zval_array_a的所有元素的zval,
找到了二个zval,一个是zval_array_0,另一个是zval_array_a。
zval_array_0的refcount是2, zval_array_a的refcount是1。
对其refcount进行减一,
zval_array_0的refcount变为1, zval_array_a的refcount变为0。
最后把zval_array_a和zval_array_0标记为灰色。步骤B完成。

进入步骤C,对root buffer中的每个zval进行遍历,
假设遍历到zval_array_a了。
使用深度优先算法,寻找zval_array_a的所有元素的zval,
找到了二个zval,一个是zval_array_0,另一个是zval_array_a。
元素zval_array_a的refcount为0,标记它为白色(图上的蓝色部分),
元素zval_array_0的refcount大于0,
标记为黑色,并且让它的refcount加一。
开始采用深度优先算法,寻找zval_array_0的子元素,如果能找到就给他们搞成黑色,再把他们的refcount加一。没找到就算了。
步骤C结束。

进入步骤D,root buffer被清空。同时,白色的zval—-zval_array_a被删除掉。
垃圾回收完成。

对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作,之后如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。

默认情况下,php 5.3中的垃圾回收是被启用的。可以在php.ini中对其进行设置:zend.enable_gc。
关闭GC之后,php仍然会把zval扔到root buffer中,但不会对其进行分析和处理,
如果root buffer满了,新的zval将不会再进入root buffer中。

The reason why possible roots are recorded even if the mechanism has been disable is because it’s faster to record possible roots, than to have to check whether the mechanism is turned on every time a possible root could be found。

之所以在GC关闭的时候,还会把zval扔到root buffer中,是因为这种操作速度很快,
比每次refcount-1的时候都去检测GC是否开启还要快一些。

由于垃圾回收的整个过程还是比较消耗时间的,如果我们的部分程序对时间很敏感,
那么有必要在程序中实时地关闭垃圾回收,PHP的开发者也想到了这一点:
除了在php.ini中对其设置,我们还可以在PHP代码里面使用gc_enable()和gc_disable()来开启/关闭垃圾回收。
通过gc_collect_cycles()函数,可以强制进行一次垃圾回收,不管root buffer有没有满。

在gc_disable()之前,最好先去调用一下gc_collect_cycles(),
把可回收的垃圾都回收一下。

在这篇文章里面,我们讲了garbage collection 垃圾回收是怎样运作的,以及在PHP中如何控制垃圾回收,
在下一篇文章里面,我们会进行一些评测Benchmark,来看一下GC的性能如何。

0 0