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读取数据库信息的几种方法
May 24 PHP
php GD绘制24小时柱状图
Jun 28 PHP
PHP 表单提交给自己
Jul 24 PHP
php 搜索框提示(自动完成)实例代码
Feb 05 PHP
简单的php数据库操作类代码(增,删,改,查)
Apr 08 PHP
php过滤XSS攻击的函数
Nov 12 PHP
PHP获取文件的MD5值并判断是否被修改的例子
Jun 19 PHP
2014年最新推荐的10款 PHP 开发框架
Aug 01 PHP
php视频拍照上传头像功能实现代码分享
Oct 08 PHP
yii2带搜索功能的下拉框实例详解
May 12 PHP
PHP设计模式之单例模式原理与实现方法分析
Apr 25 PHP
Yii2结合Workerman的websocket示例详解
Sep 10 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 empty,isset,is_null判断比较(差异与异同)
2010/10/19 PHP
destoon实现调用自增数字从1开始的方法
2014/08/21 PHP
php中strtotime函数用法详解
2014/11/15 PHP
php读取der格式证书乱码解决方法
2015/06/22 PHP
php执行多个存储过程的方法【基于thinkPHP】
2016/11/08 PHP
css把超出的部分显示为省略号的方法兼容火狐
2008/07/23 Javascript
js下用eval生成JSON对象
2010/09/17 Javascript
Jquery进度条插件 Progress Bar小问题解决
2011/07/12 Javascript
JavaScript禁止页面操作的示例代码
2013/12/17 Javascript
jquery如何扑捉回车键触发的事件
2014/04/24 Javascript
jQuery实现的省市联动菜单功能示例【测试可用】
2017/01/13 Javascript
Express+Nodejs 下的登录拦截实现代码
2017/07/01 NodeJs
微信小程序 同步请求授权的详解
2017/08/04 Javascript
五步轻松实现JavaScript HTML时钟效果
2020/03/25 Javascript
微信小程序图片轮播组件gallery slider使用方法详解
2018/01/31 Javascript
node.js基础知识小结
2018/02/26 Javascript
layui表格内容溢出的解决方法
2019/09/06 Javascript
vuex存储token示例
2019/11/11 Javascript
解决Python selenium get页面很慢时的问题
2019/01/30 Python
对django中foreignkey的简单使用详解
2019/07/28 Python
python PyQt5/Pyside2 按钮右击菜单实例代码
2019/08/17 Python
Python接口开发实现步骤详解
2020/04/26 Python
python tkiner实现 一个小小的图片翻页功能的示例代码
2020/06/24 Python
Keras 数据增强ImageDataGenerator多输入多输出实例
2020/07/03 Python
HTML5 Canvas标签使用收录
2009/07/07 HTML / CSS
番木瓜健康和保健产品第一大制造商:Herbal Papaya
2017/04/25 全球购物
Staples英国官方网站:办公用品一站式采购
2017/10/06 全球购物
介绍一下HTTP、HTTPS和SSL
2012/12/16 面试题
优秀中学生事迹材料
2014/01/31 职场文书
董事长助理岗位职责
2014/02/18 职场文书
2014年社区庆元旦活动方案
2014/03/08 职场文书
俞敏洪励志演讲稿
2014/04/29 职场文书
小学教研工作总结2015
2015/05/13 职场文书
Windows下载并安装MySQL8.0.x 版本的完整教程
2022/04/10 MySQL
python+opencv实现目标跟踪过程
2022/06/21 Python
解决Git推送错误non-fast-forward的方法
2022/06/25 Servers