最近又回顾了一下,PHP的垃圾回收机制,记录下来。实际上PHP的官方文档已经写的十分清楚了,还有鸟哥的博客。在此,仅作为记录,学习笔记。

      引用计数:

 每个php变量存在一个叫"zval"的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。

第一个   是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变 量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。

第二个额外字节是"refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope),那些主脚本(比如:通过浏览器请求的的脚本)和每个函数或者方法也都有作用域。

     下面通过代码来更形象清晰的了解:

      使用到的函数:

memory_get_usage    分配给 PHP 的内存
xdebug_debug_zval   获取变量的zval的记录信息(需要打开xdebug扩展)

代码:

var_dump(memory_get_usage());
$str = 'This is the test';
var_dump(memory_get_usage());
xdebug_debug_zval('str');

执行结果:

图中:第一,二行是内存使用情况,str   变量名         refcount     1  (计数器)              is_ref   0  (引用计数器)

情况二:赋值新变量

var_dump(memory_get_usage());
$str = 'This is the test';
var_dump(memory_get_usage());
$a = $str;
var_dump(memory_get_usage());


xdebug_debug_zval('str','a');

运行结果:

如图所示:refcount  发生变化  1   ==》  2,因为  $a = $str ,进行了赋值,但是并不是重新申请一份内存,而是指向同一个zval,也就是,存储的值还是在同一个地方,没有冗余重复的数据,这样就节省了一定的内存空间。refcount  的变化,也是为了后续垃圾回收机制的实现,提供了方便。

情况三:使用引用   &

var_dump(memory_get_usage());
$str = 'This is the test';
var_dump(memory_get_usage());
$a =& $str;
var_dump(memory_get_usage());


xdebug_debug_zval('str','a');

运行结果:

如图所示:在引用的时候,is_ref   也发生变化,为1了。

èªå¼ç¨(curcular reference,èªå·±æ¯èªå·±çä¸ä¸ªåç´ )çæ°ç»çzval

此图摘自:PHP官方网站。此图很明显执行了递归操作。指向了原始数组。

以上情况已经可以简单清晰知道了计数器的计算方法原则。下面的情况解释内存泄露。

首先,要明白一点,使用unset函数,会执行计数器逆运算,减  1  的操作。

情况四:unset   计数器  -1

var_dump(memory_get_usage());
$str = 'This is the test';
var_dump(memory_get_usage());
$a = $str;
var_dump(memory_get_usage());
unset($str);

xdebug_debug_zval('str','a');

运行结果:

此图和情况二对比,可发现  refcount 重新回归为1,如果对其中一个变量发生值的变化,同样也会变化。

情况五:数组形式,复杂的

A.

$arr = array( 'one' );
$arr[] =& $arr;
xdebug_debug_zval( 'arr' );

 运行结果:

B.

$arr = array( 'This is the test ' );
$arr[] =& $arr;
unset($arr[0]);
xdebug_debug_zval( 'arr');

此时,B  比  A  多执行了一次 unset操作,按理说,应该是refcount  1 , is_ref    0  .但是实际执行情况是:

结果却是:refcount  2 , is_ref    1.  这样就导致一个问题,这两个值都不为0,就不存在回收了。那么就造成了内存溢出,如果要分析算法,那么将耗费不少内存。

还好:php每次执行完脚本就会释放内存,清除数据结构。

敬请关注:垃圾回收周期。循环引用在php5.2的时候存在,在5.3的时候已经优化,同步回收算法。

 

我为人人,人人为我;美美与共,天下大同;

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐