当前位置:首页 > PHP > 正文内容

PHP7的垃圾回收机制

phpmianshi2年前 (2018-06-10)PHP152

垃圾回收机制


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

垃圾的产生


PHP7 中复杂类型,像字符串、数组、对象等的数据结构中,头部都有一个 gc, 这个 gc 的作用就是用来对垃圾回收的支持。当变量赋值、传递时,会增加 value 的引用数, unset、return 等释放变量时再减掉引用数,减掉后如果发现 refcount 变为 0 则直接释放 value,这是变量的基本回收过程。


不过有一种问题是这个机制无法解决的,就是循环引用的问题。


什么是循环引用呢? 简单说就是变量的内部里存的 value 又引用了变量自身。 这种比较经常发生在数组和对象类型的变量上。


这里先讲一下引用,即 zend_reference 这个类型,这个是 PHP7 新增的变量类型,当对变量使用 “&” 操作时,会创建新的中间结构体 zend_reference,这个结构体会真正的指向对应的 value 结构。


举个例子:


// 当进行如下赋值操作时
$a = 'hello'; // $a -> zend_string
$b = $a; // $b,$a -> zend_string
$c = &$b; // $c,$b -> zval(type = IS_REFERENCE, refcount = 2) -> zend_string


最终会变成如下这样:


即 $b 和 $c 的 zval 是通过中间结构体 zend_reference 再指向最终的 zend_string。


回到循环引用的问题,举个数组循环引用例子:


$a = [1];
$a[] = &$a;
unset($a);


使用 & 操作之后,变量 a 就变成了引用类型且引用计数 refcount 为 2,而又赋值给自己里面的元素,即变量 a 变成了自己引用自己。


具体如下如所示:


当 unset 之后就变成下图这样:


即 $a 所在的 zval 类型已经变成了 IS_UNDEF 了,zend_reference 结构体的引用计数减 1,但是仍然大于 0,这时候,这部分结构体就变成了垃圾,对此不处理的话,就可能会造成内存泄露。这里就需要垃圾收集器将这部分收集到缓冲区,之后进行回收处理。

回收过程


如果当变量的 refcount 减小后大于 0,PHP 并不会立即对这个变量进行垃圾鉴定和回收,而是放入一个缓冲区中,等这个缓冲区满了以后 (10000 个值) 再统一进行处理,加入缓冲区的是变量 zend_value 里的 gc,目前垃圾只会出现在数组和对象两种类型中,数组的情况上面已经介绍了,对象的情况则是成员属性引用对象本身导致的,其它类型不会出现这种变量中的成员引用变量自身的情况,所以垃圾回收只会处理这两种类型的变量。


gc 的结构 zend_refcounted_h 具体如下:



typedef struct _zend_refcounted_h {
    uint32_t         refcount; // 记录 zend_value 的引用数
    union {
        struct {
            zend_uchar    type,  // zend_value的类型, 与zval.u1.type一致
            zend_uchar    flags, 
            uint16_t      gc_info // GC信息,记录在 gc 池中的位置和颜色,垃圾回收的过程会用到
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;


一个变量只能加入一次缓冲区,为了防止重复加入,变量加入后会把 zend_refcounted_h.gc_info 置为 GC_PURPLE,即标为紫色,后续不会重复插入。


垃圾缓冲区是一个双向链表,等到缓存区满了以后则启动垃圾检查过程:遍历缓冲区,对当前变量的所有成员进行遍历,然后把成员的 refcount 减 1 (如果成员还包含子成员则也进行递归遍历,即深度优先遍历),最后再检查当前变量的引用,如果减为了 0 则为垃圾。这个算法的原理核心是:垃圾是由于成员引用自身导致的,那么就对所有的成员减一遍引用,如果发现最后变量本身的 refcount 变为了 0 则就表明其引用全部来自自身成员,即其他任何地方都不再使用它,那么它就是垃圾,需要被回收掉。反之说明不是垃圾,需要将其从缓冲区移出去。具体的过程如下:


(1) 从缓冲区链表的 roots 开始遍历,把当前 value 标为灰色 (zend_refcounted_h.gc_info 置为 GC_GREY),然后对当前 value 的成员进行深度优先遍历,把成员 value 的 refcount 减 1,并且也标为灰色;


(2) 重复遍历缓冲区链表,检查当前 value 引用是否为 0,为 0 则表示确实是垃圾,把它标为白色 (GC_WHITE),如果不为 0 则排除了引用全部来自自身成员的可能,表示还有外部的引用,并不是垃圾,这时候因为步骤 (1) 对成员进行了 refcount 减 1 操作,需要再还原回去,对所有成员进行深度遍历,把成员 refcount 加 1,同时标为黑色;


(3) 再次遍历缓冲区链表,将非 GC_WHITE 的节点从 roots 链表中移出,最终 roots 链表中全部为真正的垃圾,最后将这些垃圾清除。



php5和php7垃圾回收上的区别

PHP5和PHP7的垃圾回收机制都属于引用计数,但是在复杂数据类型的算法处理上:PHP7中zval有了新的实现方式。

最基础的变化就是 *zval 需要的内存不再是单独从堆上分配,不再自己存储引用计数。复杂数据类型(比如字符串、数组和对象)的引用计数由其自身来存储。

这种实现方式有以下好处:

1、简单数据类型不需要单独分配内存,也不需要计数;

2、不会再有两次计数的情况。在对象中,只有对象自身存储的计数是有效的;

3、由于现在计数由数值自身存储,所以也就可以和非 zval 结构的数据共享,比如 zval 和 hashtable key 之间。

什么叫做引用计数?

由于PHP是用C来写的,C里面有一种东西叫做结构体,我们PHP的变量在C中就是用这种方式存储的。

每个PHP的变量都存在于一个叫做zval的容器中,一个zval容器,除了包含变量名和值,还包括两个字节的额外信息:

● 一个叫做'is_ref',是个布尔值,用来表示这个变量是否属于引用集合,通过这个字节,我们php才能把普通变量和引用变量区分开来。

● 第二个额外字节就是'refcount',用来表示指向这个容器的变量的个数。

PHP5 与 PHP7 引用计数的对比

php 5.* 变量赋值等操作引用计数如图所示,在倒数第二步,会形成一个循环引用,并且在 unset 操作之后,会产生垃圾。

1571470962691629.png

PHP 7 的计数放到了具体的 value 中,zval 不存在写时复制(写时分离)。

并且 PHP 7 的有一个专门的 zend_reference 用来表示引用。

1571470987432602.png

以上就是php5和php7垃圾回收上的区别是什么


版权声明:本文由PHP面试资料网发布,如需转载请注明出处。
分享给朋友:

相关文章

PHP中跨域原理以及解决方案

1.为啥出现跨域在制定Html规则时,为了安全的考虑,一个源的脚本(网页,网站)不能与另一个源的资源进行交互,所以就引发一个词叫做“同源策略”。所谓同源(即指在同一个域),就是两个页面具有相同的协议(...

Windows下nginx+fastcgi+php的并发阻塞问题

同事接到一个需求,需要调用同一个项目的另一个接口,于是CURL调用接口。代码撸完了,本地测试一下 浏览器一直转圈圈直到超时…… 百思不得其解主要是windows+nginx开发环境遇到的问题,本人很少...

记一次tp3.2.3因fetch导致的漏洞处理过程

记一次tp3.2.3因fetch导致的漏洞处理过程

问题描述:最近发现百度收录大幅度下降,并出现大量5xx错误,有些收录页面直接跳转到其他网站,如下图:问题追查1.根据以往经验首先怀疑网站被植入或者挂马      &nbs...

PHP中max_execution_time设置不生效

问题描述:max_execution_time设置了1秒,但是发现超过3秒的脚本还是跑。于是深入研究下max_execution_time不生效的原因。官网描述:https://www.php.net...

PHP内核分析之生命周期五个阶段(四)

一、模块初始化阶段我们先来看一下该阶段的每个函数的作用。1.1、sapi_initialize_request_empty函数// main/SAPI.c SAPI_API vo...

PHP内核分析之生命周期(三)

一、概览PHP生命周期有五个阶段,分别为模块初始化阶段、请求初始化阶段、执行阶段、请求关闭阶段、模块关闭阶段。只是不同SAPI模式下,请求的情况略有不同,比如FastCGI下只经历了一次模块初始化阶段...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。