V8引擎了解

简介:

使用C++开发,将javascript代码转为原生机器码,使用内联缓存提高性能,编译型语言(源代码 -> 抽象语法树 -> JIT -> 本地代码)

垃圾回收,内联缓存,隐藏类

其他javascript引擎:转换成字节码或解释执行 (源代码 -> 抽象语法树 -> 字节码 -> JIT -> 本地代码)

字节码(Bytecode):

​ 一种包含执行程序、由一序列op代码、数据对组成的二进制文件,是一种中间码,编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的机器码。

原生机器码(machine code):

​ 也被称为原生码(Native Code),是电脑的CPU可直接解读的数据,机器码是计算机可以直接执行,并且执行速度最快的代码。编出的程序全部是0和1组成的指##### 令代码

字节码装换成原生机器码:首先编译器将源码编译成字节码,特定平台上 的虚拟机(如:Java虚拟机)器将字节码转译成可以直接执行的指令。字节码的典型应用为Java bytecode

V8的5.9版本,新增了Ignition字节码解释器,默认启动,原因是减轻机器码占用的内存空间,提高代码的启动速度,重构V8的代码并降低代码复杂度。

5.9之前的版本:直译成原生机器码,并使用如内联缓存等方法提高性能

V8中的指针包含三类:隐藏类指针,是V8为js对象创建的隐藏类;属性值表指针,指向该对象包含的属性值;元素表指针,指向该对象包含的属性。

工作过程

V8中,js代码是在需要执行时才进行编译,而不是一次性全部编译;这也就提高了响应时间。源代码先被解析器转成抽象语法树(AST),然后使用JIT编译器的全代码生成器将其直接生成本地客执行代码。为了提升性能,V8在生成本地代码后,使用数据分析器(profiler)采集一些信息,然后根据这些信息优化本地代码,如果优化后代码性能更差,就会优化回滚。

在执行编译前,V8会构建众多全局对象,并加载一些内置的库(如math库),来构建运行环境。

优化回滚

V8没有经过中间表示层的优化,即先编译确定变量类型等,所以引入Crankshaft编译器对热点函数,基于javascript源代码,进行优化分析。Crankshaft为了性能考虑,默认代码稳定且变量类型不变,生成高效的本地代码;但若遇到变量类型在执行过程中改变的情况,V8会将该编译器做的优化进行回滚,即重新从源码开始再次编译。

内联缓存

正常访问对象的过程:首先获取隐藏类的地址,然后根据属性名查找偏移量,然后计算该属性的地址。多次变量存取,就要重复执行这一过程,也较耗时。因此,V8提供了内嵌缓存,即将初次查找的隐藏类和偏移量保存起来,当下次查找相同对象时,可以省略计算地址的过程。但是如果一个对象有多个属性,缓存失误的概率就会提高,因为某个属性的类型变化后,对象的隐藏类也会发生变化,就与之前的缓存不一致,需要重新计算。

垃圾回收

简单分为三步:

  1. 可访问性
    从 GC Roots 对象出发,遍历 GC Root 中的所有对象:

    • 可访问对象:通过 GC Root 遍历到的对象,我们就认为该对象是可访问的(reachable),那么必须保证这些对象应该在内存中保留。

    • 不可访问对象:通过 GC Roots 没有遍历到的对象,则是不可访问的(unreachable),并会对其坐上标记,那么这些不可访问的对象就可能被回收。

    浏览器环境中,GC Root 有很多,通常包括了以下几种 (但是不止于这几种):全局的 window 对象(位于每个 iframe 中);文档 DOM 树,由可以通过遍历文档到达的所有原生 DOM 节点组成;存放栈上变量。

  2. 回收不可访问对象所占据的内存

    • 回收被标记为不可访问的对象
  3. 内存整理

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

目前 V8 采用了两个垃圾回收器,主垃圾回收器和副垃圾回收器。

在 V8 中,会把堆分为新生代(新生代通常只支持 1~8M 的容量)和老生代(容量大)两个区域,新生代中存放的是生存时间短的对象,老生代中存放生存时间久的对象。新生代又分为对象区域 、空闲区域。

在这里插入图片描述

副垃圾回收器

  1. 负责新生代的垃圾回收,大多数小的对象都会被分配到新生代,垃圾回收比较频繁。
    在这里插入图片描述

  2. 新生代中的垃圾数据用 Scavenge 算法来处理。分为两个区域:对象区域 ,空闲区域。

  3. 垃圾回收过程:

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

    1. 垃圾标记和清理:首先要对对象区域中的垃圾做标记;标记完成之后,就进入垃圾清理阶段
      副垃圾回收器会把这些我们仍然在用的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来,在复制过程,相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了。
    2. 角色翻转:完成复制后,进行角色翻转。把原来的对象区变成空闲区,把原来的空闲区变成对象区。

主垃圾回收器

  1. 负责老生代中的垃圾回收,大多数占用空间大、存活时间长的对象都会被分配到老生代里。

  2. 老生代中的垃圾数据用——标记 - 清除算法进行垃圾回收,因为老生代中的对象通常比较大,复制大对象非常耗时,会导致回收执行效率不高,所以采用标记清除法。
    在这里插入图片描述

  3. 垃圾回收过程:

    1. 标记:标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
    2. 清除:它和副垃圾回收器的垃圾清除过程完全不同,主垃圾回收器会直接将标记为垃圾的数据清理掉。
    3. 整理:清除后会产生大量不连续的内存碎片,过多的碎片会导致大对象无法分配到足够的连续内存,于是需要引进另一种算法——标记 - 整理

    在这里插入图片描述

优化垃圾回收器

由于 JavaScript 是运行在主线程之上的,在垃圾回收时会阻塞 JavaScript 脚本的执行,会造成页面卡顿等问题,使得用户体验不佳。

为了解决上述问题,V8 团队推出了并行、并发和增量等垃圾回收技术,这些技术主要是从两方面来解决垃圾回收效率问题的:

  • 1.将一个完整的垃圾回收的任务拆分成多个小的任务,解决单个垃圾回收时间长的问题。
  • 2.将标记对象、移动对象等任务转移到后台线程进行,减少主阻塞线程的时间。
并行回收
  • 如果只有一个主线程进行垃圾回收,会造成停顿时间过长。所以 V8 团队推出主线程在执行垃圾回收的任务时,引入多个辅助线程来并行处理,这样就会加速垃圾回收的执行速度,如下图:
    在这里插入图片描述

  • 副垃圾回收器所采用的就是并行策略,它在执行垃圾回收的过程中,启动了多个线程来负责新生代中的垃圾清理操作,这些线程同时将对象空间中的数据移动到空闲区域。由于数据的地址发生了改变,所以还需要同步更新引用这些对象的指针

增量回收
  • 并行回收虽然能增加垃圾回收效率,但是还是一种阻塞的方式进行垃圾回收,试想下引老生代中存在一个很大的对象,还是会造成一个长时间暂停。
  • 增量回收采用将标记工作把垃圾回收工作分解为更小的块,每次只进行小部分垃圾回收,减少主线程阻塞时间,如下图:
    在这里插入图片描述
并发回收
  • 虽然增量回收已经能大大降低我们主线程阻塞的时间,但是所有的标记和清除还是在主线程上。那有没有办法可以在不阻塞主线程情况下执行呢?也由此 V8 推出了并发回收。
  • 并发回收,是指主线程在执行 JavaScript 的过程中,辅助线程能够在后台完成执行垃圾回收的操作,如下图:
    在这里插入图片描述

在实际的应用中,这三种回收机制通常是融合在一起用的。

其他

当我们把一段JS代码丢给 V8 虚拟机时, V8 会先首先准备代码的运行时环境,这个环境包括堆空间和栈空间、全局执行上下文、全局作用域、内置的内建函数、宿主环境提供的扩展函数和对象、消息循环系统。

执行上下文中主要包含三部分,变量环境、词法环境和 this 关键字 。比如在浏览器的环境中,全局执行上下文中就包括了 window 对象,还有默认指向 window 的 this 关键字,另外还有一些 Web API 函数,诸如 setTimeout、XMLHttpRequest 等API。

全局执行上下文在 V8 的生存周期内是不会被销毁的,它会一直保存在堆中,这样当下次在需要使用函数或者全局变量时,就不需要重新创建了。

v5.9以前

javascript -> 未优化机器码 : 耗时,内存溢出,但是执行机器码很快

V8引入二进制代码缓存,内存缓存与硬盘缓存编译后的二进制代码,容易消耗大部分内存,造成内存溢出

v5.9以后

javascript -> 字节码 -> 优化后的机器码:

缓存字节码,内存占用小

虽然采用字节码在执行速度上稍慢于机器代码

但是采用字节码除了降低内存之外,还提升了代码的启动速度,并降低了代码的复杂度,而牺牲的仅仅是一点执行效率。
在这里插入图片描述
整个编译流水线的流程依次为:

  1. 初始化基础环境;
  2. 解析源码生成 AST 和作用域;
  3. 依据 AST 和作用域生成字节码;
  4. 解释执行字节码;
  5. 监听热点代码;
  6. 优化热点代码为二进制的机器代码;
  7. 反优化生成的二进制机器代码。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mosowe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值