共计 3906 个字符,预计需要花费 10 分钟才能阅读完成。
什么是援用计数
在 PHP 的数据结构中,援用计数就是指每一个变量,除了保留了它们的类型和值之外,还额定保留了两个内容,一个是以后这个变量是否被援用,另一个是援用的次数。为什么要多保留这样两个内容呢?当然是为了垃圾回收(GC)。也就是说,当援用次数为 0 的时候,这个变量就没有再被应用了,就能够通过 GC 来进行回收,开释占用的内存资源。任何程序都不能无限度的始终占用着内存资源,过大的内存占用往往会带来一个重大的问题,那就是内存泄露,而 GC 就是 PHP 底层主动帮咱们实现了内存的销毁,而不必像 C 一样必须去手动地 free。
怎么查看援用计数?
咱们须要装置 xdebug 扩大,而后应用 xdebug_debug_zval() 函数就能够看到指定内存的详细信息了,比方:
$a = "I am a String";
xdebug_debug_zval('a');
// a: (refcount=1, is_ref=0)='I am a String'
从上述内容中能够看出,这个 $a 变量的内容是 I am a String 这样一个字符串。而括号中的 refcount 就是援用次数,is_ref 则是阐明这个变量是否被援用。咱们通过变量赋值来看看这个两个参数是如何变动的。
$b = $a;
xdebug_debug_zval('a');
// a: (refcount=1, is_ref=0)='I am a String'
$b = &$a;
xdebug_debug_zval('a');
// a: (refcount=2, is_ref=1)='I am a String'
当咱们进行一般赋值后,refcount 和 is_ref 没有任何变动,但当咱们进行援用赋值后,能够看到 refcount 变成了 2,is_ref 变成了 1。这也就是说明以后的 \$a 变量被援用赋值了,它的内存符号表服务于 $a 和 $b 两个变量。
$c = &$a;
xdebug_debug_zval('a');
// a: (refcount=3, is_ref=1)='I am a String'
unset($c, $b);
xdebug_debug_zval('a');
// a: (refcount=1, is_ref=1)='I am a String'
$b = &$a;
$c = &$a;
$b = "I am a String new";
xdebug_debug_zval('a');
// a: (refcount=3, is_ref=1)='I am a String new'
unset($a);
xdebug_debug_zval('a');
// a: no such symbol
持续减少一个 $c 的援用赋值,能够看到 refcount 会持续减少。而后 unset 掉 $b 和 $c 之后,refcount 复原到了 1,不过这时须要留神的是,is_ref 仍然还是 1,也就是说,这个变量被援用过,这个 is_ref 就会变成 1,即便援用的变量都曾经 unset 掉了这个值仍然不变。
最初咱们 unset 掉 $a,显示的就是 no such symbol 了。以后变量曾经被销毁不是一个能够用的符号援用了。(留神,PHP 中的变量对应的是内存的符号表,并不是真正的内存地址)
对象的援用计数
和一般类型的变量一样,对象变量也是应用同样的计数规定。
// 对象援用计数
class A{
}
$objA = new A();
xdebug_debug_zval('objA');
// objA: (refcount=1, is_ref=0)=class A { }
$objB = $objA;
xdebug_debug_zval('objA');
// objA: (refcount=2, is_ref=0)=class A { }
$objC = $objA;
xdebug_debug_zval('objA');
// objA: (refcount=3, is_ref=0)=class A { }
unset($objB);
class C{
}
$objC = new C;
xdebug_debug_zval('objA');
// objA: (refcount=1, is_ref=0)=class A {}
不过这里须要留神的是,对象的符号表是建设的连贯,也就是说,对 $objC 进行从新实例化或者批改为 NULL,并不会影响 $objA 的内容,这方面的常识咱们在之前的 对象赋值在 PHP 中到底是不是援用?文章中曾经有过阐明。对象进行一般赋值操作也是援用类型的符号表赋值,所以咱们不须要加 & 符号。
数组的援用计数
// 数组援用计数
$arrA = [
'a'=>1,
'b'=>2,
];
xdebug_debug_zval('arrA');
// arrA: (refcount=2, is_ref=0)=array (// 'a' => (refcount=0, is_ref=0)=1,
// 'b' => (refcount=0, is_ref=0)=2
// )
$arrB = $arrA;
$arrC = $arrA;
xdebug_debug_zval('arrA');
// arrA: (refcount=4, is_ref=0)=array (// 'a' => (refcount=0, is_ref=0)=1,
// 'b' => (refcount=0, is_ref=0)=2
// )
unset($arrB);
$arrC = ['c'=>3];
xdebug_debug_zval('arrA');
// arrA: (refcount=2, is_ref=0)=array (// 'a' => (refcount=0, is_ref=0)=1,
// 'b' => (refcount=0, is_ref=0)=2
// )
// 增加一个曾经存在的元素
$arrA['c'] = &$arrA['a'];
xdebug_debug_zval('arrA');
// arrA: (refcount=1, is_ref=0)=array (// 'a' => (refcount=2, is_ref=1)=1,
// 'b' => (refcount=0, is_ref=0)=2,
// 'c' => (refcount=2, is_ref=1)=1
// )
调试数组的时候,咱们会发现两个比拟有意思的事件。
一是数组外部的每个元素又有独自的本人的援用计数。这也比拟好了解,每一个数组元素都能够看做是一个独自的变量,但数组就是这堆变量的一个哈希汇合。如果在对象中有成员变量的话,也是一样的成果。当数组中的某一个元素被 & 援用赋值给其余变量之后,这个元素的 refcount 会减少,不会影响整个数组的 refcount。
二是数组默认上来的 refcount 是 2。其实这是 PHP7 之后的一种新的个性,当数组定义并初始化后,会将这个数组转变成一个不可变数组(immutable array)。为了和一般数组辨别开,这种数组的 refcount 是从 2 开始起步的。当咱们批改一下这个数组中的任何元素后,这个数组就会变回一般数组,也就是 refcount 会变回 1。这个大家能够本人尝试下,对于为什么要这样做的问题,官网的解释是为了效率,具体的原理可能还是须要深挖 PHP7 的源码能力通晓。
对于内存泄露须要留神的中央
其实 PHP 在底层曾经帮咱们做好了 GC 机制就不须要太关怀变量的销毁开释问题,然而,千万要留神的是对象或数组中的元素是能够赋值为本身的,也就是说,给某个元素赋值一个本身的援用就变成了循环援用。那么这个对象就根本不太可能会被 GC 主动销毁了。
// 对象循环援用
class D{public $d;}
$d = new D;
$d->d = $d;
xdebug_debug_zval('d');
// d: (refcount=2, is_ref=0)=class D {// public $d = (refcount=2, is_ref=0)=...
// }
// 数组循环援用
$arrA['arrA'] = &$arrA;
xdebug_debug_zval('arrA');
// arrA: (refcount=2, is_ref=1)=array (// 'a' => (refcount=0, is_ref=0)=1,
// 'b' => (refcount=0, is_ref=0)=2,
// 'arrA' => (refcount=2, is_ref=1)=...
// )
不论是对象还是数组,在打印调试时呈现了 … 这样的省略号,那么你的程序中就呈现了循环援用。在之前的文章 对于 PHP 中对象复制的那点事儿 中咱们也讲过这个循环援用的问题,所以这个问题应该是咱们在日常开发中应该时刻关注的问题。
总结
援用计数是理解垃圾回收机制的前提条件,而且正是因为古代语言中都有一套相似的垃圾回收机制才让咱们的编程变得更加容易且平安。那么有人说了,日常开发基本用不到这些呀?用不到不代表不应该去学习,就像循环援用这个问题一样,当代码中充斥着大量的相似代码时,零碎解体只是迟早的事件,所以,这些常识是咱们向更高级的程序进阶所不可或缺的内容。
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/php/202004/source/PHP%E7%9A%84%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0%E6%98%AF%E4%BB%80%E4%B9%88%E6%84%8F%E6%80%9D%EF%BC%9F.php
参考文档:
https://www.php.net/manual/zh/features.gc.refcounting-basics.php
https://ask.csdn.net/questions/706390
https://www.jianshu.com/p/52450a61354d
各自媒体平台均可搜寻【硬核项目经理】