PHP进阶学习之垃圾回收机制详解


Posted in PHP onJune 18, 2019

本文实例讲述了PHP垃圾回收机制。分享给大家供大家参考,具体如下:

一、概念

垃圾回收机制是一种动态存储分配的方案。它会自动释放程序不再需要的已分配的内存块。垃圾回收机制可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻辑。在现在的流行各种语言当中,垃圾回收机制是新一代语言所共有的特征,如Python、PHP、C#、Ruby等都使用了垃圾回收机制。

二、PHP垃圾回收机制

1、在PHP5.3版本之前,使用的垃圾回收机制是单纯的“引用计数”。即:
①每个内存对象都分配一个计数器,当内存对象被变量引用时,计数器+1;
②当变量引用撤掉后(执行unset()后),计数器-1;
③当计数器=0时,表明内存对象没有被使用,该内存对象则进行销毁,垃圾回收完成。
并且PHP在一个生命周期结束后就会释放此进程/线程所占的内容,这种方式决定了PHP在前期不需要过多考虑内存的泄露问题。 

但是当两个或多个对象互相引用形成环状后,内存对象的计数器则不会消减为0;这时候,这一组内存对象已经没用了,但是不能回收,从而导致内存泄露的现象。
php5.3开始,使用了新的垃圾回收机制,在引用计数基础上,实现了一种复杂的算法,来检测内存对象中引用环的存在,以避免内存泄露。

2、随着PHP的发展,PHP开发者的增加以及其所承载的业务范围的扩大,在PHP5.3中引入了更加完善的垃圾回收机制,新的垃圾回收机制解决了无法处理循环的引用内存泄漏问题。

如官方文档所说:每个php变量存在一个叫"zval"的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是"refcount",用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope)。简单的理解如下图所示:

PHP进阶学习之垃圾回收机制详解

如官方文档所说,可以使用Xdebug来检查引用计数情况:

<?php
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
unset( $b, $c );
xdebug_debug_zval( 'a' );
?>

以上例程会输出:

a: (refcount=3, is_ref=0)='new string'
a: (refcount=1, is_ref=0)='new string'

注意:从PHP7的NTS版本开始,以上例程的引用将不再被计数,即$c=$b=$a之后a的引用计数也是1.具体分类如下:
在PHP 7中,zval可以被引用计数或不被引用。在zval结构中有一个标志确定了这一点。
对于null,bool,int和double的类型变量,refcount永远不会计数;
②对于对象、资源类型,refcount计数和php5的一致;
对于字符串,未被引用的变量被称为“实际字符串”。而那些被引用的字符串被重复删除(即只有一个带有特定内容的被插入的字符串)并保证在请求的整个持续时间内存在,所以不需要为它们使用引用计数;如果使用了opcache,这些字符串将存在于共享内存中,在这种情况下,您不能使用引用计数(因为我们的引用计数机制是非原子的);
对于数组,未引用的变量被称为“不可变数组”。其数组本身计数与php5一致,但是数组里面的每个键值对的计数,则按前面三条的规则(即如果是字符串也不在计数);如果使用opcache,则代码中的常量数组文字将被转换为不可变数组。再次,这些生活在共享内存,因此不能使用refcounting。

我们的demo例子如下:

<?php
echo '测试字符串引用计数';
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
unset( $b);
xdebug_debug_zval( 'a' );
$b = &$a;
xdebug_debug_zval( 'a' );
echo '测试数组引用计数';
$c = array('a','b');
xdebug_debug_zval( 'c' );
$d = $c;
xdebug_debug_zval( 'c' );
$c[2]='c';
xdebug_debug_zval( 'c' );
echo '测试int型计数';
$e = 1;
xdebug_debug_zval( 'e' );

看到的输出如下:

PHP进阶学习之垃圾回收机制详解

可以参考:https://stackoverflow.com/questions/34764119/confusion-about-php-7-refcount

三、回收周期

默认的,PHP的垃圾回收机制是打开的,然后有个php.ini设置允许你修改它:zend.enable_gc 。

当垃圾回收机制打开时,算法会判断每当根缓存区存满时,就会执行循环查找。根缓存区有固定的大小,默认10,000,可以通过修改PHP源码文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然后重新编译PHP,来修改这个值。当垃圾回收机制关闭时,循环查找算法永不执行,然而,根将一直存在根缓冲区中,不管在配置中垃圾回收机制是否激活。

除了修改配置zend.enable_gc ,也能通过分别调用gc_enable() 和 gc_disable()函数在运行php时来打开和关闭垃圾回收机制。调用这些函数,与修改配置项来打开或关闭垃圾回收机制的效果是一样的。即使在可能根缓冲区还没满时,也能强制执行周期回收。你能调用gc_collect_cycles()函数达到这个目的。这个函数将返回使用这个算法回收的周期数。

允许打开和关闭垃圾回收机制并且允许自主的初始化的原因,是由于你的应用程序的某部分可能是高时效性的。在这种情况下,你可能不想使用垃圾回收机制。当然,对你的应用程序的某部分关闭垃圾回收机制,是在冒着可能内存泄漏的风险,因为一些可能根也许存不进有限的根缓冲区。因此,就在你调用gc_disable()函数释放内存之前,先调用gc_collect_cycles()函数可能比较明智。因为这将清除已存放在根缓冲区中的所有可能根,然后在垃圾回收机制被关闭时,可留下空缓冲区以有更多空间存储可能根。

四、性能影响

1、内存占用空间的节省

首先,实现垃圾回收机制的整个原因是为了一旦先决条件满足,通过清理循环引用的变量来节省内存占用。在PHP执行中,一旦根缓冲区满了或者调用gc_collect_cycles() 函数时,就会执行垃圾回收。

2、执行时间增加

垃圾回收影响性能的第二个领域是它释放已泄漏的内存耗费的时间。
通常,PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。

3、在平常脚本中有循环回收机制运行的情况下,内存的节省将允许更多这种脚本同时运行在你的服务器上。因为总共使用的内存没达到上限。
这种好处在长时间运行脚本中尤其明显,诸如长时间的测试套件或者daemon脚本此类。同时,对通常比Web脚本运行时间长的脚本应用程序,新的垃圾回收机制,应该会大大改变一直以来认为内存泄漏问题难以解决的看法。

希望本文所述对大家PHP程序设计有所帮助。

PHP 相关文章推荐
PHP Ajax中文乱码问题解决方法
Feb 27 PHP
dedecms 批量提取第一张图片最为缩略图的代码(文章+软件)
Oct 29 PHP
PHP5中使用PDO连接数据库的方法
Aug 01 PHP
《PHP编程最快明白》第五讲:php目录、文件操作
Nov 01 PHP
php数组函数序列之in_array() - 查找数组中是否存在指定值
Nov 07 PHP
php计算十二星座的函数代码
Aug 21 PHP
如何使用php脚本给html中引用的js和css路径打上版本号
Nov 18 PHP
PHP表单数据写入MySQL数据库的代码
May 31 PHP
Laravel重写用户登录简单示例
Oct 08 PHP
PHP在线打包下载功能示例
Oct 15 PHP
[原创]php使用strpos判断字符串中数字类型子字符串出错的解决方法
Apr 01 PHP
PHP常用的类封装小结【4个工具类】
Jun 28 PHP
PHP进阶学习之命名空间基本用法分析
Jun 18 #PHP
PHP进阶学习之反射基本概念与用法分析
Jun 18 #PHP
Laravel使用RabbitMQ的方法示例
Jun 18 #PHP
thinkphp整合系列之极验滑动验证码geetest功能
Jun 18 #PHP
PHP Trait代码复用类与多继承实现方法详解
Jun 17 #PHP
php设计模式之装饰模式应用案例详解
Jun 17 #PHP
php设计模式之策略模式应用案例详解
Jun 17 #PHP
You might like
php开发工具之vs2005图解
2008/01/12 PHP
实战mysql导出中文乱码及phpmyadmin导入中文乱码的解决方法
2010/06/11 PHP
解析PHPExcel使用的常用说明以及把PHPExcel整合进CI框架的介绍
2013/06/24 PHP
CodeIgniter安全相关设置汇总
2014/07/03 PHP
Discuz7.2版的faq.php SQL注入漏洞分析
2014/08/06 PHP
浅谈使用 Yii2 AssetBundle 中 $publishOptions 的正确姿势
2017/11/08 PHP
PHP中使用CURL发送get/post请求上传图片批处理功能
2018/10/15 PHP
filemanage功能中用到的common.js
2007/04/08 Javascript
Jquery.LazyLoad.js修正版下载,实现图片延迟加载插件
2011/03/12 Javascript
js获取class的所有元素
2013/03/28 Javascript
JS仿百度搜索自动提示框匹配查询功能
2013/11/21 Javascript
jquery如何实现锚点链接之间的平滑滚动
2013/12/02 Javascript
JavaScript设计模式之外观模式实例
2014/10/10 Javascript
javascript常用函数归纳整理
2014/10/31 Javascript
jquery的总体架构分析及实现示例详解
2014/11/08 Javascript
深入理解JavaScript系列(26):设计模式之构造函数模式详解
2015/03/03 Javascript
多个jQuery版本共存的处理方案
2015/03/17 Javascript
BootStrap 超链接变按钮的实现方法
2016/09/25 Javascript
Javascript动画效果(1)
2016/10/11 Javascript
js打开word文档预览操作示例【不是下载】
2019/05/23 Javascript
vue-cli3+ts+webpack实现多入口多出口功能
2019/05/30 Javascript
vue组件 keep-alive 和 transition 使用详解
2019/10/11 Javascript
[04:29]【TI9采访】OG.N0tail在胜者组决赛后接受采访
2019/08/25 DOTA
Python里disconnect UDP套接字的方法
2015/04/23 Python
对python中Librosa的mfcc步骤详解
2019/01/09 Python
python买卖股票的最佳时机(基于贪心/蛮力算法)
2019/07/05 Python
python字符串格式化方式解析
2019/10/19 Python
matplotlib grid()设置网格线外观的实现
2021/02/22 Python
使用pandas读取表格数据并进行单行数据拼接的详细教程
2021/03/03 Python
new修饰符是起什么作用
2015/06/28 面试题
家长会学生家长演讲稿
2013/12/29 职场文书
小学毕业感言150字
2014/02/05 职场文书
经理助理岗位职责
2014/03/05 职场文书
优秀应届生求职信
2014/06/16 职场文书
2015年优质护理服务工作总结
2015/04/08 职场文书
投诉信回复范文
2015/07/03 职场文书