PHP7 垃圾回收机制是什么?

垃圾回收机制

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

垃圾的产生

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

最终会变成如下这样:

PHP7 垃圾回收机制是什么?

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

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

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

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

具体如下如所示:

PHP7 垃圾回收机制是什么?

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

PHP7 垃圾回收机制是什么?

即 $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 链表中全部为真正的垃圾,最后将这些垃圾清除。

原创文章,作者:HVXFM,如若转载,请注明出处:http://www.wangzhanshi.com/n/7078.html

(0)
HVXFM的头像HVXFM
上一篇 2025年1月1日 16:38:08
下一篇 2025年1月1日 16:38:10

相关推荐

  • 如何在php7项目中安装openssl扩展

    1、源码 /home/topsec/Documents/php-7.0.11 ,安装位置在 /usr/local/php7, php.ini 在/ usr/local/php7/l…

    php 2025年1月1日
  • PHP5怎么生成条形码

    该软件支持PHP4和PHP5两个版本,本文中使用的是PHP5的版本。在使用前注意要将PHP的GD模块开启。在Windows中为php_gd2.dll,Linux中为gd.so。将压…

    2025年1月2日
  • 怎么在PHP7项目中安装Swoole、Yar和Yaf

    php有什么用 php是一个嵌套的缩写名称,是英文超级文本预处理语言,它的语法混合了C、Java、Perl以及php自创新的语法,主要用来做网站开发,许多小型网站都用php开发,因…

    2025年1月1日
  • Ubuntu安装PHP7的方法是什么

    ubuntu安装php7的方法:首先通过命令“sudo apt-get install”安装PHP7以及常用扩展;然后启用Apache的php7.0模块;接着启动php7.0-fp…

    2025年1月1日
  • 如何安装php7和php5共存

    安装php7,与php5共存 起步 之前在服务器搭建了lamp环境,想换用性能更强的nginx作为服务器软件,又想将php5升级为php7.安装nginx无需赘述:sudo apt…

    php 2025年1月1日
  • PHP5对象simplexml的实例用法

    PHP5对象simplexml是一个新增的针对XML的一个对象。我们将会在这篇文章中对PHP5对象simplexml的属性和方法进行详细的介绍,希望大家能够通过本文对这新增对象有一…

    php 2025年1月2日
  • 向PHP传入参数的三种方法

    方法一:通过URL参数(GET请求) 概述:GET请求是最常见的HTTP请求方法之一,它通过将参数附加在URL的末尾来传递数据。这些数据以键值对的形式出现,并使用问号(?)和与号(…

    php 2024年12月17日
  • PHP7语言的执行原理是什么

    我们常用的高级语言有很多种,比较出名的有CC++、Python、 PHP、Go、Pascal等。而这些语言根据运行的方式不同,大体分为两种:编译型语言和解释型语言。 其中,编译型语…

    2025年1月1日
  • PHP如何优化冗余代码

    在编程中,代码的冗余是一个常见的问题,不仅增加了代码的复杂性,还降低了可读性和可维护性。对于PHP这样的语言来说,减少代码冗余同样重要,尤其是当项目规模变得越来越大时。本文将探讨如…

    php 2024年12月17日
  • PHP7如何加密扩展

    介绍 一个简洁、高性能、跨平台的 PHP7 代码加密扩展 特点 简单快速,经实测,几乎不影响性能 兼容 OPcache、Xdebug 等其他扩展 支持 Linux、macOS、Wi…

    php 2025年1月1日

发表回复

登录后才能评论