Skip to content
本页目录

V8 引擎

V8 引擎介绍

V8 引擎是 JS 引擎,与其它 JS 引擎相比,能够更快速的处理 JS 脚本。它的主要作用是将 JS 代码编译成机器码并执行,以及内存的管理(内存分配与垃圾回收)。

V8 中 JS 代码执行阶段:

  • 预分析:检查语法错误,但不生成 AST
  • 生成 AST:词法/语法分析后,生成抽象语法树,AST 为每一行代码定义键值对。初始类型标识符定义 AST 属于一个程序,然后所有代码行将定义在主体内部,主体是一个对象数组。
  • 生成字节码:基线编译器(Ignition)将 AST 转换为字节码
  • 生成机器代码:优化编译器 (Turbofan) 将字节码转换为优化的机器代码。另外,在逐行执行字节码的过程中,如果一段代码经常被执行,V8会直接将这段代码转换并保存为机器码,下次执行不需要经过字节码,优化了执行速度

V8 引擎在执行 JavaScript 的过程中,主要有两个阶段:编译和运行

JavaScript 在用户使用时完成编译和执行(C++ 与 Java 等语言需要在用户使用前先编译):V8 引擎中,JavaScript 相关代码是在某些代码需要执行时,才会进行编译
V8 引擎中,源代码先被解析器转变为抽象语法树(AST),然后使用 JIT 编译器的全代码生成器从 AST 直接生成本地可执行代码。这个过程不同于 JAVA 先生成字节码或中间表示,减少了 AST 到字节码的转换时间,提高了代码的执行速度。但由于缺少了转换为字节码这一中间过程,也就减少了优化代码的机会。

  • AOT(Ahead of time):先编译后执行
  • JIT(Just in time):边编译边执行

V8 之前,所有 JS 引擎都是解释执行方式, V8 引入了 JIT 方式,极大提高了 JS 的执行速度。

V8 中的垃圾回收

JavaScript 引擎中变量的存储位置主要有两个,栈内存和堆内存。

  • 栈内存:存放基本类型数据和引用类型数据的内存地址
  • 堆内存:存放引用类型数据。

对于不同类型的变量,栈内存和堆内存垃圾回收方式不同。

  • 栈内存的回收:调用栈上下文切换后回收栈内存,比较简单

  • 堆内存回收:V8的堆内存分为新生代内存和老年代内存。新生代内存是临时分配的内存,存在时间短,老年代内存存在时间长。

  • 新一代内存回收机制

新一代内存容量较小,64位系统下只有32M,新生代的记忆分为两部分:From 和 To 。进行垃圾回收时,先扫描 From,回收非存活对象,存活对象按顺序复制到 To,然后交换From/To,等待下一次回收

  • 老年代内存回收机制

Promotion:如果新生代的变量经过多次回收后仍然存在,则将其放入老年代的内存中 标记清除:老年代内存会先遍历所有对象并标记,然后对正在使用或强引用的对象取消标记,回收标记的对象 内存碎片整理:将对象移动到内存的一端

为什么JS比C++等语言慢,V8做了哪些优化?

JS 的问题

  • 动态类型:每次访问属性/查找方法时,都需要先检查类型;另外,动态类型在编译阶段很难优化
  • 属性访问:在 C++/Java 等语言中,方法、属性都是保存在数组中,只能通过数组位移获取,而JS是保存在对象中,每次获取都要进行 hash 查询。

针对 V8 的优化

  • 优化的JIT(即时编译):与 C++/Java 等编译型语言相比,JS是同时解释和执行的,效率低下。V8 优化了这个过程:如果一段代码被执行多次,V8 会将这段代码转换成机器码并缓存起来,下次运行时直接使用机器码。
  • 隐藏类:对于 C++ 等语言,只需几条指令就可以通过偏移获取变量信息,而JS需要进行字符串匹配,效率低下。V8借用类和偏移位置的思想,将对象划分为不同的组,即隐藏类。
  • 嵌入式缓存:即缓存对象查询的结果。一般的查询过程是:获取隐藏类地址->根据属性名找到偏移值->计算属性地址,嵌入式缓存就是这个过程结果的缓存。