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借用类和偏移位置的思想,将对象划分为不同的组,即隐藏类。
- 嵌入式缓存:即缓存对象查询的结果。一般的查询过程是:获取隐藏类地址->根据属性名找到偏移值->计算属性地址,嵌入式缓存就是这个过程结果的缓存。