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之PHP语法学习笔记1
Dec 17 PHP
php数组函数序列之array_unshift() 在数组开头插入一个或多个元素
Nov 07 PHP
浅析Apache中RewriteCond规则参数的详细介绍
Jun 30 PHP
php图像处理函数大全(推荐收藏)
Jul 11 PHP
php制作中间带自己定义图片二维码的方法
Jan 27 PHP
php获取参数的几种方法总结
Feb 18 PHP
ThinkPHP有变量的where条件分页实例
Nov 03 PHP
关于PHP中Session文件过多的问题及session文件保存位置
Mar 17 PHP
PHP 7.1新特性的汇总介绍
Dec 16 PHP
实例介绍PHP删除数组中的重复元素
Mar 03 PHP
详解php中生成标准uuid(guid)的方法
Apr 28 PHP
Laravel框架自定义分页样式操作示例
Jan 26 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
全国FM电台频率大全 - 25 云南省
2020/03/11 无线电
php的$_FILES的临时储存文件与回收机制实测过程
2013/07/12 PHP
php可生成缩略图的文件上传类实例
2014/12/17 PHP
thinkPHP5.0框架自动加载机制分析
2017/03/18 PHP
浅析php如何实现爬取数据原理
2018/09/27 PHP
PhpStorm的使用教程(本地运行PHP+远程开发+快捷键)
2020/03/26 PHP
JS网页播放声音实现代码兼容各种浏览器
2013/09/22 Javascript
js加载读取内容及显示与隐藏div示例
2014/02/13 Javascript
node.js中使用socket.io的方法
2014/12/15 Javascript
JavaScript数组随机排列实现随机洗牌功能
2015/03/19 Javascript
js同源策略详解
2015/05/21 Javascript
iPhone手机上搭建nodejs服务器步骤方法
2015/07/06 NodeJs
JQuery入门基础小实例(1)
2015/09/17 Javascript
Javascript函数式编程语言
2015/10/11 Javascript
jquery正则表达式验证(手机号、身份证号、中文名称)
2015/12/31 Javascript
完美实现八种js焦点轮播图(上篇)
2016/07/18 Javascript
关于JavaScript和jQuery的类型判断详解
2016/10/08 Javascript
jquery获取select,option所有的value和text的实例
2017/03/06 Javascript
js/jq仿window文件夹框选操作插件
2017/03/08 Javascript
Jquery EasyUI $.Parser
2017/06/02 jQuery
WebGL学习教程之Three.js学习笔记(第一篇)
2019/04/25 Javascript
微信小程序商品详情页底部弹出框
2019/11/22 Javascript
Python使用cx_Oracle模块将oracle中数据导出到csv文件的方法
2015/05/16 Python
python制作小说爬虫实录
2017/08/14 Python
python读取并定位excel数据坐标系详解
2019/06/26 Python
Python爬虫之Spider类用法简单介绍
2020/08/04 Python
python利用递归方法实现求集合的幂集
2020/09/07 Python
销售代表求职自荐信
2013/10/01 职场文书
高职教师先进事迹材料
2014/08/24 职场文书
党员专题组织生活会发言材料
2014/10/17 职场文书
新郎结婚保证书
2015/02/26 职场文书
财务部岗位职责范本
2015/04/14 职场文书
CSS3实现的侧滑菜单
2021/04/27 HTML / CSS
zabbix自定义监控nginx状态实现过程
2021/11/01 Servers
redis调用二维码时的不断刷新排查分析
2022/04/01 Redis
vue使用element-ui按需引入
2022/05/20 Vue.js