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变量容器是: ameaningnumber。增加和减少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,才做模拟恢复,每个变量只能恢复一次,恢复后标记为黑色。

  • 这样剩下一些没有被恢复的就是该删除的蓝色节点了,然后将蓝色节点遍历出来,真正删除。

posted @ 2020-11-26 17:25  phper-liunian  阅读(125)  评论(0编辑  收藏  举报