# 三.v8 引擎机制

# 1.v8 如何执行一段 js 代码

  • 1.预解析:检测语法错误但不生成 AST
  • 2.生成 AST:经过词法/语法分析,生成抽象语法树
  • 3.生成字节码:基线编译器将 AST 转换成字节码
  • 4.生成机器码:优化编译器将字节码转换成优化过的机器码,此外在逐行执行字节码的过程中,如果一段代码经常被执行,那么 V8 会将这段代码直接转换成机器码保持起来,下一次执行就不必经过字节码,优化了执行速度

# 2.引用计数和标记清除

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

# 3.v8 如何进行垃圾回收

js 引擎中对变量的存储主要有两种位置,栈内存和堆内存,栈内存存储基本类型数据及引用类型数据的内存地址,对内存存储引用类型的数据。

栈内存回收 栈内存调用栈上下文切换后就被回收 堆内存的回收 V8 的堆内存分为新生代和老生代内存,新生代内存是临时分配的内存,存在时间短,老生代内存存在时间长

  • 新生代内存回收机制
    • 新生代内存容量小,64 位系统下仅有 32M,新生代内存分为 From、To 两部分,进行垃圾回收时,先扫描 From,将非存活对象回收,将存活对象顺序复制到 To 中,之后调换 From/to,等待下一次回收
  • 老生代内存回收机制
  • 晋升:如果新生代的变量经过多次回收依然存在,那么就会被放入老生代内存中
  • 标记清除:老生代内存会先遍历所有对象并打上标记,然后对正在使用或强引用的对象取消标记,回收被标记的对象
  • 整理内存碎片;把对象挪到内存的一端

# 4.V8 做了哪些优化

  • 1.js 的问题
    • 动态类型:导致每次存取属性/寻求方法的时候,都需要先检查类型;此外动态类型也很难再编译阶段进行优化
    • 属性存取:C++/Java 等语言中方法、属性时存储在数组中的,仅需数组位移就可以获取,而 JS 存储在对象中,每次获取都要进行哈希查询
  • 2.V8 的优化
    • 优化 JIT(即时编译):相较于 C++/Java 这类编译型语言,JS 一边解释一边执行,效率低。V8 对这个过程进行了优化:如果一段代码被执行多次,那么 V8 会把这段代码转为机器码缓存下来,下次运行时直接使用机器码。
    • 隐藏类:对于 C++这类语言来说,仅需几个指令就能通过偏移量获取变量信息,而 JS 需要进行字符串匹配,效率低,V8 借用了类和偏移位置的思想,将对象划分为不同的组,即隐藏类
    • 内嵌缓存:即缓存对象查询的结果。常规查询过程是:获取隐藏类地址->根据属性名查找偏移值->计算该属性地址,内嵌缓存就是对这一过程结果的缓存
    • 垃圾回收管理

# 5.js 垃圾回收机制

  • 垃圾回收机制,执行环境负责管理代码执行过程中使用的内存。
  • 垃圾收集器会定期(周期性)找出那些不再继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行。

# 标记清除:

定义和用法:当变量进入环境是,将变量标记"进入环境",当变量离开环境是,标记为:"离开环境"。某一个时刻,垃圾回收器会过滤环境中的变量,以及被环境变量引用的变量,剩下的即使被视为准备回收的变量。

# 引用计数

定义和用法:引用计数是跟踪记录每个值被引用的计数为 0 时,被视为准备回收的对象。

基本原理:就是变量的引用次数,被引用一次则加 1,当这个引用

# js 的内存生命周期(变量的生命)

  1. 分配你所需要的空间 var a = 20
  2. 使用分配带的内存(读写)alert(a + 10) r js 的垃圾收集器每隔固定的时间就执行一次释放操作,通用的是通过标记清除的算法

在局部作用域中,垃圾回收器很容易做出判断并回收,全局比较难,因此应避免全局变量

上次更新: 2022/6/29 上午12:09:44