php垃圾回收机制
php变量存储在叫zval
的容器中,下文用变量容器说明,这个容器包含了变量类型,变量值,是否是引用变量is_ref
,容器引用次数refcount
四个部分。
引用计数机制
标准变量
将一个常量赋值给一个变量时就会创建一个变量容器,如下
<?php
$a = 'a string';
?>
如果你安装了Xdebug扩展,可以使用xdebug_debug_zval()
查看效果
<?php
$a = 'a string';
xdebug_debug_zval('a');
?>
结果为
a: (refcount=1, is_ref=0)='a string'
如果把变量$a
赋值给另一个变量$b
,不会新创建新变量容器,但是引用次数会加1,如下
<?php
$a = 'a string';
$b = $a;
xdebug_debug_zval('a');
?>
结果为
a: (refcount=2, is_ref=0)='a string'
当变量离开作用域(比如所在函数执行完),或者使用unset()方法时,对应的变量容器的refcount
会减1,当refcount
为0时,变量容器销毁,内存回收。
复合变量
<?php
$a = array('meaning' => 'life', 'number' => 42);
xdebug_debug_zval('a');
?>
上面的将产生3个变量容器,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'
)
上面的过程并不会创建新的变量容器,meaning
和life
指向同一个变量容器,所以refcount
为2,看下面的图更容易理解。
循环引用问题
<?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)=...
)
上面的过程会创建2个变量容器,其中a
和数组a索引为1的变量指向同一个变量容器,所以refcount
为2,如下图
如果对数组a执行unset操作,变量容器会如下图所示
由于变量容器的refcount
仍为1,不能为清除,同时由于没有变量在指向这个变量容器,没有办法对refcount
进行减1操作,这个变量容器会一直存在直到脚本结束,如果有大量这种变量容器存在,很可能会造成内存泄漏。为了解决这个问题,php5.3以后引入了新的垃圾回收机制,GC(Garbage Collector)。
GC机制
关于垃圾回收,主要有3条基本原则
- refcount在增加,不是垃圾
- refcount减小为0,变量容器将被清除
- refcount减小但是大于0,变量容器仍存在,此时将使用GC机制
GC机制的整个过程如下图
下面对上面的ABCD步骤进行逐一解释
A: 为了避免每当refcount减少的时候都调用GC机制,算法会把可能的变量容器(zval)都放入根缓冲区(root buffer),用紫色标记,这些变量容器是疑似垃圾。仅仅当缓冲区满的时候,才会对这些变量容器进行垃圾回收操作。
B: 对根缓冲区内所有变量容器的refcount进行减1操作,为了避免对同一变量容器重复操作,减1后标记为灰色。
C: 检查根缓冲区内所有变量容器,refcount为0的标记为白色(垃圾),refcount大于0的,将refcount进行加1操作(对B操作的还原),标记为黑色。
D: 释放标记为白色的变量容器。
整个过程可以概括为疑似垃圾放入根缓冲区,减操作,恢复操作,清除垃圾。
注意,当$a = null时,a对应的变量容器的refcount
为0。