Heim  >  Artikel  >  php教程  >  PHP的垃圾回收机制详解

PHP的垃圾回收机制详解

WBOY
WBOYOriginal
2016-06-13 09:17:231043Durchsuche

PHP的垃圾回收机制详解

最近由于使用php编写了一个脚本,模拟实现了一个守护进程,因此需要深入理解php中的垃圾回收机制。本文参考了PHP手册。

 

在理解PHP垃圾回收机制(GC)之前,先了解一下变量的存储。

 

php中变量存在于一个zval的变量容器中。结构如下:

 

 

 

 

 

类型

 

 

is_ref

 

refcount

 

 

 

 

 

zval中,除了存储变量的类型和值之外,还有is_ref字段和refcount字段。

 

is_ref:是个bool值,用来区分变量是否属于引用集合。什么意思呢,你可以这么认为:表示变量是否有一个以上的别名。 

refcount:计数器,表示指向这个zval变量容器的变量个数。 

两者之间有这么一个默认关系:当refcount值为1时,is_ref的值为false。因为refcount为1,此变量不可能有多个别名,也就不存在引用了。

 

安装xdebug拓展之后,可以利用xdebug_debug_zval打印出zval容器详情。

 

这里有一点需要注意,将一个变量 = 赋值给另一个变量时,不会立即为新变量分配内存空间,而是在原变量的zval中给refcount加1。 只有当原变量或者发生改变时,才会为新变量分配内存空间,同时原变量的refcount减 1 。当然,如果unset原变量,新变量直接就使用原变量的zval而不是重新分配。

 

 &引用赋值时,原变量的is_ref 变为1,refcount 加1.  如果给一个变量&赋值,之前 = 赋值的变量会分配空间。

 

 

 

 

$a = 1;

xdebug_debug_zval('a');

echo PHP_EOL;

$b = $a;

xdebug_debug_zval('a');

echo PHP_EOL;

 

$c = &$a;

xdebug_debug_zval('a');

echo PHP_EOL;

 

xdebug_debug_zval('b');

echo PHP_EOL;

?>

  运行结果如下:

 

a:(refcount=1, is_ref=0),int 1

 

a:(refcount=2, is_ref=0),int 1

 

a:(refcount=2, is_ref=1),int 1

 

b:(refcount=1, is_ref=0),int 1

 

 

 

 

 

上面描述的zval存储的是标量,那复合类型的数组是如何存储的呢?

 

 

$a = array( 'meaning' => 'life', 'number' => 42 );

xdebug_debug_zval( 'a' );

echo PHP_EOL;

class Test{

    public $a = 1;

    public $b = 2;

     

    function handle(){

        echo 'hehe';

    }

}

 

$test = new Test();

xdebug_debug_zval('test');

?>

  运行结果如下:

 

a:(refcount=1, is_ref=0),

 

array

  'meaning' => (refcount=1, is_ref=0),

string

 

'life' (length=4)

  'number' => (refcount=1, is_ref=0),

int

 

 42

test:(refcount=1, is_ref=0),

 

object(Test)[1]

  public 'a' => (refcount=2, is_ref=0),

int

 

 1

  public 'b' => (refcount=2, is_ref=0),

int

 

2

 

可以看出,数组用了比数组长度多1个zval存储。对象类似。下面给出了数组的存储形象表示

 

 

 

可以看到:数组分配了三个zval容器:a   meaning  number

 

现在看看所谓的环状引用是如何生成的

 

 

$a = array( 'one' );

$a[] =& $a;

xdebug_debug_zval( 'a' );

?>

  运行结果:

 

a:(refcount=2, is_ref=1),

 

array

  0 => (refcount=1, is_ref=0),

string

 

 'one' (length=3)

  1 => (refcount=2, is_ref=1), &array

 

a 和 1 的zval容器 是一样的。如下:

 

 

 

 

 

 

 

这样就形成了环状引用。

 

在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果refcount为0,那么变量的空间可以被释放,否则就不释放,这是一种非常简单的GC实现。

 

现在unset ($a),那么array的refcount减1变为1.现在无任何变量指向这个zval,而且这个zval的计数器为1,不会回收。

 

 

 

 

 

尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。庆幸的是,php将在请求结束时清除这个数据结构,但是在php清除之前,将耗费不少空间的内存。如果你要实现分析算法,或者要做其他像一个子元素指向它的父元素这样的事情,这种情况就会经常发生。当然,同样的情况也会发生在对象上,实际上对象更有可能出现这种情况,因为对象总是隐式的被引用。

 

 

 

如果上面的情况发生仅仅一两次倒没什么,但是如果出现几千次,甚至几十万次的内存泄漏,这显然是个大问题。在长时间运行的脚本,比如请求基本上不会结束的守护进程时,就会出现问题,内存空间会不断耗费,导致内存不足而崩溃。

 

 

 

PHP5.3中,采用了专门的算法(比较复杂)。,来处理环状引用导致内存泄露的问题。

 

当一个zval可能为垃圾时,回收算法会把这个zval放入一个内存缓冲区。当缓冲区达到最大临界值时(最大值可以设置),回收算法会循环遍历所有缓冲区中的zval,判断其是否为垃圾,并进行释放处理。或者我们在脚本中使用gc_collect_cycles,强制回收缓冲区中的垃圾。

 

在php5.3的GC中,针对的垃圾做了如下说明:

 

1:如果一个zval的refcount增加,那么此zval还在使用,肯定不是垃圾,不会进入缓冲区

 

2:如果一个zval的refcount减少到0, 那么zval会被立即释放掉,不属于GC要处理的垃圾对象,不会进入缓冲区。

 

 3:如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾,将其放入缓冲区。PHP5.3中的GC针对的就是这种zval进行的处理。

 

 

 

 

 

开启/关闭垃圾回收机制可以通过修改php配置实现,也可以在程序中使用gc_enable() 和 gc_disable()开启和关闭。

 

 

 

开启垃圾回收机制后,针对内存泄露的情况,可以节省大量的内存空间,但是由于垃圾回收算法运行耗费时间,开启垃圾回收算法会增加脚本的执行时间。

 

下面是php手册中给的一个脚本

 

 

class Foo

{

    public $var = '3.1415962654';

}

 

$baseMemory = memory_get_usage();

 

for ( $i = 0; $i

{

    $a = new Foo;

    $a->self = $a;

    if ( $i % 500 === 0 )

    {

        echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n";

    }

}

?>

  

 

针对这个脚本,给出了其在php5.2和5.3中内存的占用情况,如下图:

 

 

 

针对下面这个脚本

 

 

class Foo

{

    public $var = '3.1415962654';

}

 

for ( $i = 0; $i

{

    $a = new Foo;

    $a->self = $a;

}

 

echo memory_get_peak_usage(), "\n";

?>

  

 

开启垃圾回收机制,相对于不开启的时候,脚本执行时间增加了7%

 

通常,PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。

 

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn