最近又回顾了一下,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了。
此图摘自: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的时候已经优化,同步回收算法。
我为人人,人人为我;美美与共,天下大同;