加入收藏 | 设为首页 | 会员中心 | 我要投稿 开发网_开封站长网 (http://www.0378zz.com/)- 科技、AI行业应用、媒体智能、低代码、办公协同!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

畅聊PHP7 的垃圾回收机制

发布时间:2022-06-30 14:18:10 所属栏目:PHP教程 来源:互联网
导读:垃圾回收机制是一种动态存储分配方案。它会自动释放程序不再需要的已分配的内存块,这篇文章主要介绍了PHP7 的垃圾回收机制,需要的朋友可以参考下。 不过有一种问题是这个机制无法解决的,就是循环引用的问题。 什么是循环引用呢? 简单说就是变量的内部里存
  垃圾回收机制是一种动态存储分配方案。它会自动释放程序不再需要的已分配的内存块,这篇文章主要介绍了PHP7 的垃圾回收机制,需要的朋友可以参考下。
 
  不过有一种问题是这个机制无法解决的,就是循环引用的问题。
 
  什么是循环引用呢? 简单说就是变量的内部里存的 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。
 
  回到循环引用的问题,举个数组循环引用例子:
 
  $arr = [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,目前垃圾只会出现在数组和对象两种类型中,数组的情况上面已经介绍了,对象的情况则是成员属性引用对象本身导致的,其它类型不会出现这种变量中的成员引用变量自身的情况,所以垃圾回收只会处理这两种类型的变量。
       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 链表中全部为真正的垃圾,最后将这些垃圾清除。
 
 

(编辑:开发网_开封站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读