<?php $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; ?>
当缓冲区达到最大临界值时(最大值能够设置),回收算法会循环遍历全部缓冲区中的zval,判断其是否为垃圾,并进行释放处理。或者咱们在脚本中使用gc_collect_cycles,强制回收缓冲区中的垃圾。php
转自:PHP的垃圾回收机制详解
https://www.cnblogs.com/taijun/p/4206770.htmlhtml
最近因为使用php编写了一个脚本,模拟实现了一个守护进程,所以须要深刻理解php中的垃圾回收机制。本文参考了PHP手册。算法
在理解PHP垃圾回收机制(GC)以前,先了解一下变量的存储。数组
php中变量存在于一个zval的变量容器中。结构以下:数据结构
类型性能 |
值spa |
is_refdebug |
refcountcode |
zval中,除了存储变量的类型和值以外,还有is_ref字段和refcount字段。htm
二者之间有这么一个默认关系:当refcount值为1时,is_ref的值为false。由于refcount为1,此变量不可能有多个别名,也就不存在引用了。
安装xdebug拓展以后,能够利用xdebug_debug_zval打印出zval容器详情。
这里有一点须要注意,将一个变量 = 赋值给另外一个变量时,不会当即为新变量分配内存空间,而是在原变量的zval中给refcount加1。 只有当原变量或者发生改变时,才会为新变量分配内存空间,同时原变量的refcount减 1 。固然,若是unset原变量,新变量直接就使用原变量的zval而不是从新分配。
&引用赋值时,原变量的is_ref 变为1,refcount 加1. 若是给一个变量&赋值,以前 = 赋值的变量会分配空间。
<?php $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存储的是标量,那复合类型的数组是如何存储的呢?
<?php $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
如今看看所谓的环状引用是如何生成的
<?php $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手册中给的一个脚本
<?php class Foo { public $var = '3.1415962654'; } $baseMemory = memory_get_usage(); for ( $i = 0; $i <= 100000; $i++ ) { $a = new Foo; $a->self = $a; if ( $i % 500 === 0 ) { echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "\n"; } } ?>
针对这个脚本,给出了其在php5.2和5.3中内存的占用状况,以下图:
针对下面这个脚本
<?php class Foo { public $var = '3.1415962654'; } for ( $i = 0; $i <= 1000000; $i++ ) { $a = new Foo; $a->self = $a; } echo memory_get_peak_usage(), "\n"; ?>
开启垃圾回收机制,相对于不开启的时候,脚本执行时间增长了7%
一般,PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增长。可是在日常的(更小的)脚本中应根本就没有性能影响。