php的垃圾回收机制
PHP垃圾回收机制
引用计数机制
每个php变量存在于一个zval
变量容器中。一个zval变量容器, 在php5时,zval的定义如下:
struct _zval_struct {
union {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
zval变量容器,包含了变量的值和类型,还包括了is_ref
,是个布尔值,标识这个变量是否属于引用变量,refcount
表示指向zval变量容器的个数。
Example
当一个变量被赋值时,就会生成一个zval变量容器,如:
<?php
$a = 'new string'
那么在上述例子中, zval的变量信息是多少呢?
<?php
xdebug_debug_zval('a');
会显示输出:
a: (refcount=1, is_ref=0)='new string'
Example
若增加一个zval的引用计数
<?php
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
上述会显示:
a: (refcount=2, is_ref=0)='new string'
这时,引用次数是2, 因为同一变量容器被变量a和b关联,php不会复制已生成的变量容器。变量容器在refcount为0时销毁,任何关联到变量容器的变量,离开它的作用域(如函数执行结束)或调用unset()
函数,refcount就会减1。
Example
减少引用计数
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
unset( $b, $c );
xdebug_debug_zval( 'a' );
上述会显示:
a: (refcount=3, is_ref=0)='new string'
a: (refcount=1, is_ref=0)='new string'
复合类型
当变量是array或object这样的复合数据类型时,与标量类型的值不同,array和 object类型的变量把它们的成员或属性存在自己的符号表中。这意味着下面的例子将生成三个zval变量容器。
Example
$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的标量规则和上面提到的一样
Example
添加一个已经存在的元素到列表中
<?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'
)
Example
添加一个数组本身作为这个数组的元素
$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。上面的输出结果中的"..."说明发生了递归操作, 显然在这种情况下意味着"..."指向原始数组。
跟刚刚一样,对一个变量调用unset,将删除这个符号,且它指向的变量容器中的引用次数也减1。所以,如果我们在执行完上面的代码后,对变量$a调用unset, 那么变量 $a 和数组元素 "1" 所指向的变量容器的引用次数减1, 从"2"变成"1"。
Example
$a = array( 'one' );
$a[] =& $a;
unset($a);
xdebug_debug_zval( 'a' );
上述则显示:
(refcount=1, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=1, is_ref=1)=...
)
清理变量容器的问题
尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。
PHP回收周期
传统上,php用引用计数内存机制,无法处理循环引用内存泄漏。但是php5.3使用引用计数系统中同步周期回收
的同步算法,来处理内存泄漏。
这个算法的基础规则是,如果一个变量引用计数增加,它将继续使用。如果变量的引用计数减少到0,那么变量容器将会被清除。也就是说,在引用计数减少到非零值时, 才会产生垃圾周期。在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾。
回收周期步骤如下:
-
为了避免检查所有的引用计数可能减少的周期,这个算法把所有可能根,放在根缓冲区(用紫色标记,称为疑似垃圾),这样可以确保每个可能的垃圾根在缓冲区中只出现一次。仅仅在缓冲区满了时, 对缓冲区所有的不同变量容器执行垃圾回收操作。
-
模拟删除每个紫色变量。模拟删除可能将将不是紫色的普通变量引用次数减1,如果普通变量的引用次数为0,就对这个普通变量在做一次模拟删除。每个变量只能被模拟删除一次,模拟删除后标记为灰色。
-
模拟恢复每个紫色变量。恢复条件是,变量的引用计数大于0,才做模拟恢复,每个变量只能恢复一次,恢复后标记为黑色。
-
这样剩下一些没有被恢复的就是该删除的蓝色节点了,然后将蓝色节点遍历出来,真正删除。