一文搞懂V8引擎的垃圾回收机制

这篇具有很好参考价值的文章主要介绍了一文搞懂V8引擎的垃圾回收机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

我们平时在写代码的过程中,好像很少需要自己手动进行垃圾回收,那么V8是如何来减少内存占用,从而避免内存溢出而导致程序崩溃的情况的。为了更高效地回收垃圾,V8引入了两个垃圾回收器,它们分别针对不同场景进行工作。
如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,文章公众号首发,关注 前端南玖 第一时间获取最新文章~

垃圾从何而来

我们先来搞清楚这些‘垃圾’是怎么产生的

不管使用哪一种语言,我们势必都会频繁的操作数据,这些数据一般是存放在栈内存与堆内存中,通常是会在内存中创建一块空间,使用这块空间,再不需要的时候回收这块空间。

比如:

var test = {}
test.a = new Array(100)

当执行这段代码时,先会为全局对象(window)添加一个test属性,并在堆内存中创建一个空对象,并将该对象的地址指向test属性,随后又创建了一个长度为100的数组,并将该数组地址指向了test.a的属性值。

一文搞懂V8引擎的垃圾回收机制

从上图我们可以看出,栈中保存了指向window对象的指针,通过栈中window的地址可以找到window对象,通过window对象可以找到test对象,通过test对象可以找到a数组。

如果此时,我们将a属性指向了另一个对象:

test.a = {}

那么此时的内存会变成这样:

一文搞懂V8引擎的垃圾回收机制

那么这个时候堆内存中的数组其实就变成了‘垃圾数据’,因为我们再也访问不到它了,不过我们不必担心它会一直占用内存,因为V8中的垃圾回收器会帮我们自动清理。

对于 JavaScript 而言,也正是这个“自动”释放资源的特性带来了很多困惑,也让一些 JavaScript 开发者误以为可以不关心内存管理,这是一个很大的误解。

代际假说与分代收集

代纪假说是垃圾回收领域中的一个重要术语,后续垃圾回收策略都是建立在该假说之上的。

特点

  • 第一个是大部分对象在内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得不可访问

  • 第二个是不死的对象,会活得更久

为了达到最好的回收效果,V8会根据对象的生存周期的不同来应用不同的回收算法,所以在 V8 中会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象

新生区通常只支持 1~8M 的容量,而老生区支持的容量就大很多了。对于这两块区域,V8 分别使用两个不同的垃圾回收器,以便更高效地实施垃圾回收

  • 副垃圾回收器,主要负责新生代的垃圾回收

  • 主垃圾回收器,主要负责老生代的垃圾回收

垃圾回收器的工作流程

V8的内存结构

  • 新生代(new_space):大多数的对象开始都会被分配在这里,这个区域相对较小但是垃圾回收特别频繁,该区域被分为两半,一半用来分配内存,另一半用于在垃圾回收时将需要保留的对象复制过来。

  • 老生代(old_space):新生代中的对象在存活一段时间后就会被转移到老生代内存区,相对于新生代该内存区域的垃圾回收频率较低。老生代又分为老生代指针区老生代数据区,前者包含大多数可能存在指向其他对象的指针的对象,后者只保存原始数据对象,这些对象没有指向其他对象的指针。

  • 大对象区(large_object_space):存放体积超越其他区域大小的对象,每个对象都会有自己的内存,垃圾回收不会移动大对象区。

  • 代码区(code_space):代码对象,会被分配在这里,唯一拥有执行权限的内存区域。

  • map区(map_space):存放Cell和Map,每个区域都是存放相同大小的元素,结构简单

垃圾回收的过程一般主要出现在新生代老生代

垃圾回收策略

标记清除

标记清除( Mark-Sweep ),目前在 JavaScript引擎 里这种算法是最常用的,到目前为止的大多数浏览器的 JavaScript引擎 都在采用标记清除算法,只是各大浏览器厂商还对此算法进行了优化加工,且不同浏览器的 JavaScript引擎 在运行垃圾回收的频率上有所差异。 此算法分为 标记 和 清除 两个阶段,标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。

引擎在执行 GC(使用标记清除算法)时,需要从出发点去遍历内存中所有的对象去打标记,而这个出发点有很多,我们称之为一组根对象,而所谓的根对象,其实在浏览器环境中包括又不止于 全局Window对象、文档DOM树等。

整个标记清除算法大致过程就像下面这样:

  • 垃圾收集器在运行时会给内存中的所有变量都加上一个标记,假设内存中所有对象都是垃圾,全标记为0;
  • 然后从各个根对象开始遍历,把不是垃圾的节点改成1;
  • 清理所有标记为0的垃圾,销毁并回收它们所占用的内存空间;
  • 最后,把所有内存中对象标记修改为0,等待下一轮垃圾回收;

优点:

实现比较简单,打标记也无非打与不打两种情况,这使得一位二进制位(0和1)就可以为其标记,非常简单

缺点:

在清除之后,剩余的对象内存位置是不变的,也会导致空闲内存空间是不连续的,出现了 内存碎片,并且由于剩余空闲内存不是一整块,它是由不同大小内存组成的内存列表,这就牵扯出了内存分配的问题

引用计数

引用计数( Reference Counting ),这其实是早先的一种垃圾回收算法,它把对象是否不再需要简化定义为对象有没有其他对象引用到它,如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收,但因为它的问题很多,目前很少使用这种算法了。

它的策略是跟踪记录每个变量值被使用的次数

  • 当声明了一个变量并且将一个引用类型赋值给该变量的时候这个值的引用次数就为 1;
  • 如果同一个值又被赋给另一个变量,那么引用数加 1;
  • 如果该变量的值被其他的值覆盖了,则引用次数减 1;
  • 当这个值的引用次数变为 0 的时候,说明没有变量在使用,这个值没法被访问了,回收空间,垃圾回收器会在运行的时候清理掉引用次数为 0 的值占用的内存;

优点:

  • 引用计数在引用值为 0 时,也就是在变成垃圾的那一刻就会被回收,所以它可以立即回收垃圾;
  • 标记清除算法需要每隔一段时间进行一次,那在应用程序(JS脚本)运行过程中线程就必须要暂停去执行一段时间的 GC,另外,标记清除算法需要遍历堆里的活动以及非活动对象来清除,而引用计数则只需要在引用时计数就可以了;

缺点:

  • 需要一个计数器,而此计数器需要占很大的位置,因为我们也不知道被引用数量的上限;
  • 无法解决循环引用无法回收的问题;

工作流程

不论什么类型的垃圾回收器,它们都有一套相同的执行流程

  • 第一步是标记空间中活动对象和非活动对象。所谓活动对象就是还在使用的对象,非活动对象就是可以进行垃圾回收的对象。

  • 第二步是回收非活动对象所占据的内存。其实就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象。

  • 第三步是做内存整理。一般来说,频繁回收对象后,内存中就会存在大量不连续空间,我们把这些不连续的内存空间称为内存碎片。当内存中出现了大量的内存碎片之后,如果需要分配较大连续内存的时候,就有可能出现内存不足的情况。所以最后一步需要整理这些内存碎片,但这步其实是可选的,因为有的垃圾回收器不会产生内存碎片,比如接下来我们要介绍的副垃圾回收器。

副垃圾回收器

副垃圾回收器主要负责新生区的垃圾回收。而通常情况下,大多数小的对象都会被分配到新生区,所以说这个区域虽然不大,但是垃圾回收还是比较频繁的。

新生代中用 Scavenge 算法来处理。所谓 Scavenge 算法,是把新生代空间对半划分为两个区域,一半是对象区域,一半是空闲区域,如下图所示:

一文搞懂V8引擎的垃圾回收机制

新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。

在垃圾回收过程中,首先要对对象区域中的垃圾做标记;标记完成之后,就进入垃圾清理阶段,副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,所以这个复制过程,也就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了。完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域。这样就完成了垃圾对象的回收操作,同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去。

由于新生代中采用的 Scavenge 算法,所以每次执行清理操作时,都需要将存活的对象从对象区域复制到空闲区域。但复制操作需要时间成本,如果新生区空间设置得太大了,那么每次清理的时间就会过久,所以为了执行效率,一般新生区的空间会被设置得比较小。也正是因为新生区的空间不大,所以很容易被存活的对象装满整个区域。为了解决这个问题,JavaScript 引擎采用了对象晋升策略,也就是经过两次垃圾回收依然还存活的对象,会被移动到老生区中。

主垃圾回收器

主垃圾回收器主要负责老生区中的垃圾回收。除了新生区中晋升的对象,一些大的对象会直接被分配到老生区。因此老生区中的对象有两个特点,一个是对象占用空间大,另一个是对象存活时间长。

由于老生区的对象比较大,若要在老生区中使用 Scavenge 算法进行垃圾回收,复制这些大的对象将会花费比较多的时间,从而导致回收执行效率不高,同时还会浪费一半的空间。因而,主垃圾回收器是采用**标记 - 清除(Mark-Sweep)**的算法进行垃圾回收的。

它的原理就是:

  • 首先是标记过程阶段。标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
  • 接下来就是垃圾的清除过程。它和副垃圾回收器的垃圾清除过程完全不同,对一块内存多次执行标记 - 清除算法后,可能会产生大量不连续的内存碎片。

一文搞懂V8引擎的垃圾回收机制

  • 而碎片过多会导致大对象无法分配到足够的连续内存,于是又产生了另外一种算法——标记 - 整理(Mark-Compact),这个标记过程仍然与标记 - 清除算法里的是一样的,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
    一文搞懂V8引擎的垃圾回收机制

全停顿

由于 JavaScript 是运行在主线程之上的,一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。我们把这种行为叫做全停顿(Stop-The-World)

在 V8 新生代的垃圾回收中,因其空间较小,且存活对象较少,所以全停顿的影响不大,但老生代就不一样了。如果在执行垃圾回收的过程中,占用主线程时间过久,将会造成页面卡顿。

为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,我们把这个算法称为增量标记(Incremental Marking) 算法。文章来源地址https://www.toymoban.com/news/detail-480820.html

到了这里,关于一文搞懂V8引擎的垃圾回收机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【JVM】垃圾回收机制

     哈喽,哈喽,大家好~ 我是你们的老朋友: 保护小周ღ    今天给大家带来的是  JVM (Java 虚拟机) 的垃圾回收机制,回收是指回收什么?  如何确定要回收的内存: 引用计数,可达性分析,如何释放空间 : 标记清除,复制算法,标记整理,分代回收 ,一起来看看叭~ 本期

    2024年02月09日
    浏览(45)
  • java垃圾回收机制

    Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是  堆  内存中对象的分配与回收。Java 堆是垃圾收集器管理的主要区域,因此也被称作  GC 堆。 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代,中间一层属于老年代,最

    2024年02月04日
    浏览(48)
  • JavaScript垃圾回收机制

    在JavaScript中,垃圾回收(Garbage Collection)是一种自动内存管理机制,它可以自动地识别不再使用的变量和对象并将它们从内存中清除,以释放内存空间。 JavaScript中的垃圾回收器会定期扫描内存中的对象,标记那些可达对象和不可达对象。 可达对象指的是当前代码中正在被使

    2024年02月10日
    浏览(47)
  • JVM学习 GC垃圾回收机制 (堆内存结构、GC分类、四大垃圾回收算法)

    🤖 作者简介: 努力的clz ,一个努力编程的菜鸟 🐣🐤🐥   👀 文章专栏: 《JVM 学习笔记》 ,本专栏会专门记录博主在学习 JVM 中学习的知识点,以及遇到的问题。   🙉 文章详情: 本篇博客是学习 【狂神说Java】JVM快速入门篇 的学习笔记,关于 GC垃圾回收机制 (堆内存结

    2023年04月19日
    浏览(44)
  • JavaScript中的垃圾回收机制

    前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一

    2024年02月07日
    浏览(49)
  • JVM中的垃圾回收机制

    java相较于c、c++语言的优势之一是自带垃圾回收器,垃圾回收是指 不定时 去堆内存中清理 不可达 对象。不可达的对象并不会 马上 就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,程序员唯一能做的就是通过调用System.gc 方法来建议执行垃圾收集

    2024年02月16日
    浏览(49)
  • Lua的垃圾回收机制详解

    Lua 是一种轻量级的编程语言,广泛用于嵌入到其他应用程序中,尤其是在游戏开发领域。Lua 的内存管理机制采用了自动垃圾收集(Garbage Collection)的方法。以下是Lua内存管理的一些关键方面: 垃圾收集原理概述 Lua 使用的是标记-清除(Mark-and-Sweep)算法进行垃圾收集。这个

    2024年02月03日
    浏览(45)
  • JVM:垃圾回收机制(GC)

    引用计数算法:         在对象中添加一个引用计数器,当每有一个地方引用它时,计数器值加一。当引用失效时,计数器值就减一。当一个对象的计数器为零时,表示该对象没有被任何其他对象引用,因此可以被释放。 优点 :是可以及时回收垃圾对象,避免内存泄漏,且

    2024年01月19日
    浏览(48)
  • Java垃圾回收机制深入理解

    Java垃圾回收机制是Java虚拟机(JVM)的核心组件之一,对于内存管理起到至关重要的作用。它能自动追踪并管理应用程序中创建的对象,当这些对象不再使用时,垃圾回收机制会自动回收其占用的内存,使这部分内存能够被再次利用。此机制极大地减少了开发者需要手动管理

    2024年02月09日
    浏览(38)
  • Java的垃圾回收机制详解

    目录 1、C语言与Java语言垃圾回收区别 2、System.gc() 3、面试题引入Java垃圾回收 3.1 jvm怎么确定哪些对象应该进行回收 3.1.1 引用计数法 3.1.2 可达性分析算法  3.2 jvm会在什么时候进行垃圾回收的动作 3.2 jvm到底是怎么回收垃圾对象的 4、垃圾回收算法 4.1 标记-清除算法 4. 2 复制算

    2024年02月09日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包