详解PHP的引用计数

 更新时间:2021年04月28日 10:55:39   作者:硬核项目经理  
这篇文章主要介绍了PHP的引用计数的相关资料,帮助大家更好的理解和学习使用PHP,感兴趣的朋友可以了解下

什么是引用计数

在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变量被引用赋值了,它的内存符号表服务于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 掉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 ,并不会影响objC进行重新实例化或者修改为NULL,并不会影响objA 的内容,对象进行普通赋值操作也是引用类型的符号表赋值,所以我们不需要加 & 符号。

数组的引用计数

// 数组引用计数
$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)=...
// )

不管是对象还是数组,在打印调试时出现了 ... 这样的省略号,那么你的程序中就出现了循环引用。所以这个问题应该是我们在日常开发中应该时刻关注的问题。

总结

引用计数是了解垃圾回收机制的前提条件,而且正是因为现代语言中都有一套类似的垃圾回收机制才让我们的编程变得更加容易且安全。那么有人说了,日常开发根本用不到这些呀?用不到不代表不应该去学习,就像循环引用这个问题一样,当代码中充斥着大量的类似代码时,系统崩溃只是迟早的事情,所以,这些知识是我们向更高级的程序进阶所不可或缺的内容。

测试代码: github.com/zhangyue050…

以上就是详解PHP的引用计数的详细内容,更多关于PHP的引用计数的资料请关注脚本之家其它相关文章!

相关文章

  • PHP程序守护进程化实现方法详解

    PHP程序守护进程化实现方法详解

    这篇文章主要介绍了PHP程序守护进程化实现方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • php常量详细解析

    php常量详细解析

    这篇文章主要介绍了php常量,常量的语法分析,区分常量和变量的不同之处,以及什么是预定义常量,对这方面感兴趣的朋友可以借鉴借鉴。
    2015-10-10
  • 解析argc argv在php中的应用

    解析argc argv在php中的应用

    本篇文章是对argc与argv在php中的应用进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • linux平台编译安装PHP7并安装Redis扩展与Swoole扩展实例教程

    linux平台编译安装PHP7并安装Redis扩展与Swoole扩展实例教程

    这篇文章主要介绍了linux平台编译安装PHP7并安装Redis扩展与Swoole扩展的方法,结合实例形式详细分析了Linux平台上安装php7并安装Redis扩展与Swoole扩展的具体步骤与操作技巧,需要的朋友可以参考下
    2016-09-09
  • PHP四大安全策略

    PHP四大安全策略

    这篇文章主要介绍了PHP中的文件系统安全、数据库安全、用户数据安全等安全相关的问题,需要的朋友可以参考下
    2014-03-03
  • 写出高质量的PHP程序

    写出高质量的PHP程序

    这是一个很老的话题了,现在的PHP程序员都在追求所谓的高性能,但高性能并不代表是高质量,什么是高质量的PHP程序呢?我认为,高质量的PHP程序首先得具备以下的要素
    2012-02-02
  • 深入了解PHP的垃圾回收机制

    深入了解PHP的垃圾回收机制

    这篇文章主要介绍了PHP垃圾回收机制讲解,本篇文章通过简要的案例,详细讲解了该机制的原理与使用,以下就是详细内容,需要的朋友可以参考下
    2022-08-08
  • PHP中break及continue两个流程控制指令区别分析

    PHP中break及continue两个流程控制指令区别分析

    php中常用的for与foreach循环中,经常遇到条件判断或中止循环的情况。而处理方式主要用到break及continue两个流程控制指令,现在说明主要区别
    2011-04-04
  • PHP上传图片时判断上传文件是否为可用图片的方法

    PHP上传图片时判断上传文件是否为可用图片的方法

    这篇文章主要介绍了PHP上传图片时判断上传文件是否为可用图片的方法,涉及php针对图片的后缀检测操作技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2016-10-10
  • PHP5.5新特性之yield理解与用法实例分析

    PHP5.5新特性之yield理解与用法实例分析

    这篇文章主要介绍了PHP5.5新特性之yield理解与用法,结合实例形式分析了php5.5 yield生成器功能、原理、使用方法及相关操作注意事项,需要的朋友可以参考下
    2019-01-01

最新评论