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 相关文章推荐
IIS6.0+PHP5.x+MySQL5.x+Zend3.0x+GD+phpMyAdmin2.8x通用安装实例(已经完成)
Dec 06 PHP
php smarty模版引擎中变量操作符及使用方法
Dec 11 PHP
php文档更新介绍
Jul 22 PHP
php开发文档 会员收费1期
Aug 14 PHP
用php实现百度网盘图片直链的代码分享
Nov 01 PHP
ubuntu12.04使用c编写php扩展模块教程分享
Dec 25 PHP
php中利用explode函数分割字符串到数组
Feb 08 PHP
ThinkPHP3.1.3版本新特性概述
Jun 19 PHP
ThinkPHP控制器详解
Jul 27 PHP
PHP正则匹配日期和时间(时间戳转换)的实例代码
Dec 14 PHP
php使用curl获取header检测开启GZip压缩的方法
Aug 15 PHP
PHP For循环字母A-Z当超过26个字母时输出AA,AB,AC
Feb 16 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
纯真IP数据库的应用 IP地址转化成十进制
2009/06/14 PHP
PHP APC的安装与使用详解
2013/06/13 PHP
ThinkPHP中公共函数路径和配置项路径的映射分析
2014/11/22 PHP
怎么用javascript进行拖拽
2006/07/20 Javascript
页面中js执行顺序
2009/11/09 Javascript
用js实现输入提示(自动完成)的实例代码
2013/06/14 Javascript
jQuery之字体大小的设置方法
2014/02/27 Javascript
EasyUI实现二级页面的内容勾选的方法
2015/03/01 Javascript
jquery Validation表单验证使用详解
2020/09/12 Javascript
浅谈jquery.fn.extend与jquery.extend区别
2015/07/13 Javascript
jQuery超赞的评分插件(8款)
2015/08/20 Javascript
JS+CSS实现自动切换的网页滑动门菜单效果代码
2015/09/14 Javascript
微信小程序实现跟随菜单效果和循环嵌套加载数据
2017/11/21 Javascript
vue2中的keep-alive使用总结及注意事项
2017/12/21 Javascript
微信小程序chooseImage的用法(从本地相册选择图片或使用相机拍照)
2018/08/22 Javascript
Vue函数式组件-你值得拥有
2019/05/09 Javascript
Vue过渡效果之CSS过渡详解(结合transition,animation,animate.css)
2020/02/05 Javascript
一分钟学会JavaScript中的try-catch
2020/12/14 Javascript
python获取当前计算机cpu数量的方法
2015/04/18 Python
Python基于pandas实现json格式转换成dataframe的方法
2018/06/22 Python
python 快速把超大txt文件转存为csv的实例
2018/10/26 Python
Python定时器线程池原理详解
2020/02/26 Python
Django REST framwork的权限验证实例
2020/04/02 Python
加拿大租车网站:Enterprise Rent-A-Car
2018/07/26 全球购物
static关键字的用法
2013/10/07 面试题
趣味运动会活动方案
2014/02/12 职场文书
演讲稿的写法
2014/05/19 职场文书
企业总经理任命书
2014/06/05 职场文书
党员先进事迹材料
2014/12/19 职场文书
2015年师德师风自我评价范文
2015/03/05 职场文书
当幸福来敲门观后感
2015/06/01 职场文书
物业保洁员管理制度
2015/08/05 职场文书
女方家长婚礼答谢词
2015/09/29 职场文书
2019年XX公司的晨会制度及流程!
2019/07/23 职场文书
详解Python函数print用法
2021/06/18 Python
一篇文章弄懂Python中的内建函数
2021/08/07 Python