V8 引擎 垃圾回收机制

发布于 2024-09-07 20:27:04 字数 4005 浏览 8 评论 0

V8 的垃圾回收是发生在什么时候?

浏览器 渲染页面的空闲时间 进行垃圾回收。

V8 引擎的垃圾内存回收机制

我们都知道,所有的 对象类型的数据在 JS 中都是通过堆进行空间分配的 ,当我们构造一个对象进项赋值操作的时候,其实相应的内存已经分配到了堆上,我们可以不断地这样创建对象,让 V8 为它分配空间,直到堆的大小达到上限。

v8 内存限制

V8 为什么要给它设置内存上限?

究其根本,是由两个因素共同决定的

  • 一个是 JS 单线程的执行机制
  • 另一个是 JS 垃圾回收机制的限制

首先 JS 是单线程运行的,这意味着 一旦进入到垃圾回收,那么其它的各种运行逻辑都要暂停 ; 另一方面垃圾回收其实是非常耗时间的操作,V8 官方是这样形容的:

以 1.5GB 的垃圾回收堆内存为例,V8 做一次小的垃圾回收需要 50ms 以上,做一次非增量式的垃圾回收甚至要 1s 以上。

可见其耗时之久,而且在这么长的时间内,我们的 JS 代码执行会一直没有响应,造成应用卡顿,导致应用性能和响应能力直线下降。因此,V8 做了一个简单粗暴的 选择,那就是限制堆内存

V8 是如何进行垃圾回收的?

JS 引擎中对 变量的存储主要有两种位置,栈内存和堆内存

栈内存的回收

栈内存 调用栈上下文 切换后,栈顶的空间就会自动被回收。

堆内存的回收

v8 把堆内存划分为两部分进行处理———— 新生代内存老生代内存 两个区域。

顾名思义,新生代 就是临时分配的内存,存活时间短; 老生代 是常驻内存,存活的时间长。

根据这两种不同种类的堆内存,v8 采用了不同的回收策略,来根据不同的场景做针对性的优化

新生代 的垃圾回收是怎么做的呢?

  1. 首先将新生代内存空间一分为二,如图

img

其中的 From 部分表示正在使用的内存,To 是目前闲置的内存

当进行垃圾回收时,v8 将 From 部分的对象检查一遍

  • 如果是 存活对象 ,那么直接复制到 To 内存中( 在 To 内存中按照顺序从头放置的
  • 如果是 非存活对象 ,直接回收即可

当所有的 From 中的存活对象按照顺序进入到 To 内存之后,From 和 To 两者的角色 对调 ,From 现在被闲置,To 为正在使用,如此循环。

那你很可能会问了,直接将非存活对象回收了不就万事大吉了嘛,为什么还要后面的一系列操作?

注意,我刚刚特别说明了,在 To 内存中按照顺序从头放置的,这是为了应对这样的场景:

img

深色的小方块代表存活对象,白色部分表示待分配的内存,由于堆内存是连续分配的,这样零零散散的空间可能会导致稍微大一点的对象没有办法进行空间分配, 这种零散的空间也叫做 内存碎片 。刚刚介绍的新生代垃圾回收算法也叫 Scavenge 算法 。Scavenge 算法主要就是解决 内存碎片 的问题,在进行一顿复制之后,To 空间变成了这个样子:

img

老生代内存的回收

如果新生代中的变量 经过多次回收后依然存在 ,那么就会被放入到 老生代内存 中,这种现象就叫 晋升

发生晋升其实不只是这一种原因,我们来梳理一下会有哪些情况触发 晋升

  • 已经经历过一次 Scavenge 回收。
  • To(闲置)空间的内存占用超过 25%。

现在进入到 老生代 的垃圾回收机制当中, 老生代 中累计的变量空间一般都是很大的,当然不能用 Scavenge 算法啦,因为它不仅会 浪费一半空间 ,还会 对庞大的内存空间进行复制 呢。

第一步:

​ 进行 标记-清除 。主要分为两个阶段,即 标记阶段清除阶段

​ 首先会遍历堆中的所有对象,对它们作上标记,然后对 代码环境中 使用的变量 以及被 强引用 的变量取消标记 (因为它们属于被引用对象), 剩下的就是要删除的变量了 ,在随后的 清除阶段 对其进行空间的回收。

当然这又会 引发内存碎片的问题 ,存活对象的空间不连续对后续的空间分配造成障碍。老生代又是如何处理这个问题的呢?

第二步:

整理 内存碎片 。v8 的解决方式非常简单粗暴,在 清除阶段 结束后,把存活的对象全部往一端靠拢。由于是移动对象,它的执行速度不可能很快,事实上也是整个过程中最耗时间的部分。

增量标记

由于 JS 的单线程机制 ,V8 在进行垃圾回收的时候,不可避免地会 阻塞业务逻辑 的执行,倘若 老生代 的垃圾回收任务很重,那么耗时会非常可怕,严重影响应用的性能。 那这个时候为了避免这样问题,V8 采取了 增量标记 的方案, 即将一口气完成的标记任务分为很多小的部分完成,每做完一个小的部分就"歇"一下,就 js 应用逻辑执行一会儿, 然后再执行下面的部分 ,如果循环,直到标记阶段完成才进入内存碎片的整理上面来。其实这个过程跟 React Fiber 的思路有点像.经过 增量标记 之后,垃圾回收过程对 JS 应用的阻塞时间减少到原来了 1 / 6, 可以看到,这是一个非常成功的改进。

介绍一下引用计数和标记清除

  • 引用计数 :给一个变量赋值引用类型,则该对象的引用次数+1,如果这个变量变成了其他值,那么该对象的引用次数-1,垃圾回收器会回收引用次数为 0 的对象。但是当对象 循环引用 时,会导致引用次数永远无法归零,造成内存无法释放。
  • 标记清除: 垃圾收集器先给 内存中所有对象加上标记,然后从根节点开始遍历,去掉被引用的对象和运行环境中对象的标记,剩下的被标记的对象就是无法访问的,等待垃圾回收的对象。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

0 文章
0 评论
22 人气
更多

推荐作者

linfzu01

文章 0 评论 0

可遇━不可求

文章 0 评论 0

枕梦

文章 0 评论 0

qq_3LFa8Q

文章 0 评论 0

JP

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文