聊聊~PHP的GC

首先要说明,如果是普通类型的变量(int、float、boolean),当你执行,unset的时候,是直接删除的。
PHP垃圾回收机制(Garbage Conllector 简称 GC)在PHP中,没有任何变量指向这个对象时,这个对象就是垃圾。PHP会将其在内存中销毁;这是PHP的GC垃圾处理机制,防止内存溢出。当一个PHP线程结束时,当前占用的所有内存空间都会被销毁,当前程序中所有对象同时被销毁。
php7的垃圾回收包含两个部分,一个是垃圾收集器,一个是垃圾回收算法。
垃圾收集器,把刚刚提到的,可能是垃圾的元素收集到回收池中 也就是把变量的 zend_refcount的信息 放在回收池中。 当回收池的值达到一定额度了,会进行统一处理。
处理的过程呢,就比较简单。
遍历回收池中的每一个变量,根据每一个变量,再遍历每一个成员,如果成员还有嵌套的话继续遍历。然后把所有成员的 做模拟的 refcount -1。如果此时外部的变量的 引用次数为 0 。那么可以视为垃圾,清楚。如果大于0,那么恢复引用次数,并从垃圾回收池中取出。
引用计数基本知识
每个PHP变量存在一个叫做’zval’的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息,第一个是’is_ref’,它是个bool值,用来表示这个变量是否为引用集合(reference set)。通过这个字节,PHP引擎才能把普通变量和引用变量区分开。由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制来优化内存使用。第二个额外字节是"refcount",用来表示指向这个zval变量容器的变量(也称符号即symbol)个数。
举个例子:

<?php
$hxg = "坏小哥";
?>

上面变量hxg,是在当前作用域生成的。并且生成类型为string和值为new string 的变量容器。此时,'is_ref’被设置为FALSE,因为没有任何自定义的引用生成。'refcount’被设定为1,因为这里只有一个变量使用这个变量容器。并且当’refcount’为1的时候,'is_ref’总是为FALSE。我们安装Xdebug,调用 xdebug_debug_zval()显示"refcount"和"is_ref"的值。

<?php
xdebug_debug_zval('hxg');
?>

上面程序会输出===>hxg: (refcount=1, is_ref=0)=‘坏小哥’。把一个变量赋值给另一个变量将增加引用次数(refcount)。
例如增加一个引用次数:

$hxg = "坏小哥";
$ztf = $hxg;
xdebug_debug_zval( 'hxg' );

上面程序会输出===>hxg: (refcount=2, is_ref=0)=‘坏小哥’
此时,引用次数是2,因为同一个变量容器被变量 a 和变量 b关联.当没必要时,php不会去复制已生成的变量容器。变量容器在”refcount“变成0时就被销毁. 当任何关联到某个变量容器的变量离开它的作用域(比如:函数执行结束),或者对变量调用了函数 unset()时,”refcount“就会减1。

<?php
$hxg = "坏小哥";
$lj = $ztf = $hxg;
xdebug_debug_zval( 'hxg' );
unset( $ztf, $lj );
xdebug_debug_zval( 'hxg' );
?>

上面程序输出:
hxg: (refcount=3, is_ref=0)=‘坏小哥’
hxg: (refcount=1, is_ref=0)=‘坏小哥’
如果我们现在执行 unset($hxg);,包含类型和值的这个变量容器就会从内存中删除。

复合类型:
array和 object类型的变量把它们的成员或属性存在自己的符号表中。这意味着下面的例子将生成三个zval变量容器。

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
?>

上面程序输出:
a: (refcount=1, is_ref=0)=array (
‘meaning’ => (refcount=1, is_ref=0)=‘life’,
‘number’ => (refcount=1, is_ref=0)=42
)
在这里插入图片描述
这三个zval变量容器是: a,meaning和 number。增加和减少”refcount”的规则和上面提到的一样. 下面, 我们在数组中再添加一个元素,并且把它的值设为数组中已存在元素的值:

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
xdebug_debug_zval( 'a' );
?>

上面程序输出===>a: (refcount=1, is_ref=0)=array (
‘meaning’ => (refcount=2, is_ref=0)=‘life’,
‘number’ => (refcount=1, is_ref=0)=42,
‘life’ => (refcount=2, is_ref=0)=‘life’
)
在这里插入图片描述
我们看到原有的数组元素和新添加的数组元素关联到同一个"refcount"2的zval变量容器. 尽管 Xdebug的输出显示两个值为’life’的 zval 变量容器,其实是同一个。
删除数组中的一个元素,就是类似于从作用域中删除一个变量. 删除后,数组中的这个元素所在的容器的“refcount”值减少,同样,当“refcount”为0时,这个变量容器就从内存中被删除。

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
unset( $a['meaning'], $a['number'] );
xdebug_debug_zval( 'a' );
?>

上面程序输出===> a: (refcount=1, is_ref=0)=array (
‘life’ => (refcount=1, is_ref=0)=‘life’
)

好了,现在开始讨论一个重要的问题,如果一个数组,引用自己怎么处理?

<?php
$a = array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );
?>

上面程序输出===>a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)=‘one’,
1 => (refcount=2, is_ref=1)=…
)
在这里插入图片描述
能看到数组变量 (a) 同时也是这个数组的第二个元素(1) 指向的变量容器中“refcount”为 2。上面的输出结果中的"…“说明发生了递归操作, 显然在这种情况下意味着”…"指向原始数组。
PHP5.3.0之后解决循环引用问题
如果引用计数减少到零,所在变量容器将被清除(free),不属于垃圾
如果一个zval 的引用计数减少后还大于0,那么它会进入垃圾周期。仅仅在引用计数减少到非零值时,才会产生垃圾周期(garbage cycle)。其次,在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾。
在这里插入图片描述
A: 把所有可能根(possible roots 都是zval变量容器),放在根缓冲区(root buffer)中(用紫色来标记),保证每个可能的垃圾根在缓冲区只出现一次。当缓冲区满了,进行垃圾回收操作。
B: 模拟删除每个紫色变量,算法以深度优先对每一个节点所包含的zval进行减1操作(在对对类遍历时,将继承类和自己的两个对象成员属性表进行合并),为了确保不会对同一个zval的refcount重复执行减1操作,一旦zval的refcount减1之后会将zval标记成灰色。需要强调的是,这个步骤中,起初节点zval本身不做减1操作,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作。
C: 算法再次以深度优先判断每一个节点包含的zval的值,如果zval的refcount等于0,那么将其标记成蓝色(代表垃圾),如果zval的refcount大于0,那么将对此zval以及其包含的zval进行refcount加1操作,这个是对非垃圾的还原操作,同时将这些zval的颜色变成黑色。
D: 删除蓝色节点。
根缓存区有固定的大小,可存10,000个可能根,当然你可以通过修改PHP源码文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然后重新编译PHP,来修改这个10,000值。
当然PHP也为我们准备了手动来操作GC的函数
通过分别调用gc_enable() 和 gc_disable()函数来打开和关闭垃圾回收机制。调用这些函数,与修改配置项来打开或关闭垃圾回收机制的效果是一样的。即使在可能根缓冲区还没满时,也能强制执行周期回收。你能调用gc_collect_cycles()函数达到这个目的。这个函数将返回使用这个算法回收的周期数。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章