0

0

什么是引用计数?深入浅析PHP中的引用计数!

青灯夜游

青灯夜游

发布时间:2021-07-05 19:14:55

|

3230人浏览过

|

来源于掘金社区

转载

什么是引用计数?怎么查看引用计数?怎么用引用计数?下面本篇文章就来带大家了解一下引用计数,介绍一下引用计数的使用方法。

什么是引用计数?深入浅析PHP中的引用计数!

什么是引用计数

在PHP的数据结构中,引用计数就是指每一个变量,除了保存了它们的类型和值之外,还额外保存了两个内容,一个是当前这个变量是否被引用,另一个是引用的次数。为什么要多保存这样两个内容呢?当然是为了垃圾回收(GC)。

也就是说,当引用次数为0的时候,这个变量就没有再被使用了,就可以通过 GC 来进行回收,释放占用的内存资源。

任何程序都不能无限制的一直占用着内存资源,过大的内存占用往往会带来一个严重的问题,那就是内存泄露,而 GC 就是PHP底层自动帮我们完成了内存的销毁,而不用像 C 一样必须去手动地 free 。

立即学习PHP免费学习笔记(深入)”;

怎么查看引用计数?

我们需要安装 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会继续增加。然后unsetc 的引用赋值,可以看到 refcount 会继续增加。然后 unset 掉b 和 $c 之后,refcount 恢复到了1,不过这时需要注意的是,is_ref 依然还是1,也就是说,这个变量被引用过,这个 is_ref 就会变成1,即使引用的变量都已经 unset 掉了这个值依然不变。

最后我们 unset 掉 $a ,显示的就是 no such symbol 了。当前变量已经被销毁不是一个可以用的符号引用了。(注意,PHP中的变量对应的是内存的符号表,并不是真正的内存地址)

kgogoprime
kgogoprime

KGOGOMall 是一套采用 Php + MySql 开发的基于 WEB 应用的 B/S 架构的B2C网上商店系统。具有完善的商品管理、订单管理、销售统计、新闻管理、结算系统、税率系统、模板系统、搜索引擎优化,数据备份恢复,会员积分折扣功能,不同的会员有不同的折扣,支持多语言,模板和代码分离等,轻松创建属于自己的个性化用户界面。主要面向企业和大中型网商提供最佳保障,最大化满足客户目前及今后的独立

下载

对象的引用计数

和普通类型的变量一样,对象变量也是使用同样的计数规则。

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

不管是对象还是数组,在打印调试时出现了 ... 这样的省略号,那么你的程序中就出现了循环引用。在之前的文章 关于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://www.jianshu.com/p/52450a61354d
本文转载自:https://juejin.cn/post/6950093123682828301作者:硬核项目经理

推荐学习:《PHP视频教程

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

930

2023.08.02

c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

252

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

1008

2024.03.01

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

698

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

219

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1561

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

646

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

1148

2024.03.22

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

4

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 12.9万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 11.3万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 1.0万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号