PHP和Java一样都有一个垃圾收集器,变量的回收是不需要手动回收的,这大大简化了我们的开发难度,它是PHP高级面试的题目之一。
原理
PHP中垃圾收集器的原理采用的是引用计数的方法。
- 如果引用计数减少到0,所在变量容器将被清除,不属于垃圾;
- 如果引用计数减少后还大于0,它将进入垃圾回收周期;
- 在垃圾回收周期中,检查引用计数是否减1,如果减到0了,那这些变量容器则是垃圾需要回收。
也就是说当你的引用计数第一次减0后我就不管了直接给你free,如果减少还不是0,php的垃圾收集器就盯上你了,将你视为后面需要垃圾回收的对象。在后续的观察过程中如果计数器变为了0,垃圾收集器就把你回收了。
zval内存结构
在php zval中用来判断垃圾收回的有两个变量它们分别是is_ref和refcount。
变量名 | 解释 |
is_ref | 是否有地址类型的引用 |
refcount | 引用次数 |
例子
开启xdebug扩展可以看到引用计数的变量is_ref和refcount,下面分别就字符串和数组类型写一个例子。
以下是php7.3的例子
1) 字符串类型的引用
<?php
echo "字符串类型引用\n";
$s1 = "yxjc123.com";
$s2 = $s1;
xdebug_debug_zval('s1');
$s3 = $s1;
xdebug_debug_zval('s1');
unset($s2);
xdebug_debug_zval('s1');
$s2 = &$s1;
xdebug_debug_zval('s1');
输出:
字符串类型引用
s1: (refcount=1, is_ref=0)='yxjc123.com'
s1: (refcount=1, is_ref=0)='yxjc123.com'
s1: (refcount=1, is_ref=0)='yxjc123.com'
s1: (refcount=2, is_ref=1)='yxjc123.com'
s1: (refcount=1, is_ref=0)='yxjc123.com'
s1: (refcount=1, is_ref=0)='yxjc123.com'
s1: (refcount=1, is_ref=0)='yxjc123.com'
s1: (refcount=2, is_ref=1)='yxjc123.com'
从上面的结果看出字符串类型的引用,字符串引用计数refcount不变,总是1,地址引用会加1,且is_ref变为1。
2) 数组类型的引用
<?php
echo '测试数组引用'.PHP_EOL;
$arr1 = array('a','b');
xdebug_debug_zval('arr1');
$arr2 = $arr1;
xdebug_debug_zval('arr1');
$arr3 = $arr1;
xdebug_debug_zval('arr1');
unset($arr2);
xdebug_debug_zval('arr1');
$arr1[2]='c';
xdebug_debug_zval('arr1');
$arr4 = $arr1;
xdebug_debug_zval('arr1');
$arr5 = &$arr1;
xdebug_debug_zval('arr1');
输出:
测试数组引用
arr1: (refcount=2, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
arr1: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
arr1: (refcount=4, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
arr1: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
arr1: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b', 2 => (refcount=1, is_ref=0)='c')
arr1: (refcount=2, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b', 2 => (refcount=1, is_ref=0)='c')
arr1: (refcount=2, is_ref=1)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b', 2 => (refcount=1, is_ref=0)='c')
arr1: (refcount=2, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
arr1: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
arr1: (refcount=4, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
arr1: (refcount=3, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b')
arr1: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b', 2 => (refcount=1, is_ref=0)='c')
arr1: (refcount=2, is_ref=0)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b', 2 => (refcount=1, is_ref=0)='c')
arr1: (refcount=2, is_ref=1)=array (0 => (refcount=1, is_ref=0)='a', 1 => (refcount=1, is_ref=0)='b', 2 => (refcount=1, is_ref=0)='c')
数组类型的引用refcount根据引用的次数加1,地址引用refcount不变,
数组值修改后之前引用refcount无效,重新计算。
3) int类型的引用
<?php
echo 'int类型的引用'.PHP_EOL;
$a = 1;
xdebug_debug_zval('a');
$b = $a;
xdebug_debug_zval('a');
$c = &$a;
xdebug_debug_zval('a');
$d = &$a;
xdebug_debug_zval('a');
输出:int类型的引用
a: (refcount=0, is_ref=0)=1
a: (refcount=0, is_ref=0)=1
a: (refcount=2, is_ref=1)=1
a: (refcount=3, is_ref=1)=1
a: (refcount=0, is_ref=0)=1
a: (refcount=0, is_ref=0)=1
a: (refcount=2, is_ref=1)=1
a: (refcount=3, is_ref=1)=1
int类型的引用refcount不变,总是0,地址引用会加1,且is_ref变为1。