Skip to content

V8

解释器和执行器

解释器和执行器
  • 在编译型语言的编译过程中,编译器首先会依次对源代码进行词法分析、语法分析,生成抽象语法树(AST),然后是优化代码,最后再生成处理器能够理解的机器码。如果编译成功,将会生成一个可执行的文件。但如果编译过程发生了语法或者其他的错误,那么编译器就会抛出异常,最后的二进制文件也不会生成成功

  • 在解释型语言的解释过程中,同样解释器也会对源代码进行词法分析、语法分析,并生成抽象语法树(AST),不过它会再基于抽象语法树生成字节码,最后再根据字节码来执行程序、输出结果

V8 执行代码

v8 执行代码

生成抽象语法树和执行上下文

  • 分词(tokenize),词法分析

将源码拆成一个个 token(语法上不能再分的、最小的单个字符或字符串)

  • 解析(parse),语法分析

将上一步生成的 token 数据,根据语法规则转成抽象语法树(存在语法错误,会终止抛出错误)

生成字节码

字节码是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行(体积小)

执行代码

解释器 Ignition

  • 第一次执行的字节码,解释器逐条解释执行
  • 解释器解析生成字节码,还有一个作用就是解释执行字节码
  • 执行字节码过程中,有热点代码(重复执行多次),后台编译器(TurboFan)把这段代码编译成机器码,提高执行效率

JIT 即时编译

解释器 Ignition 在解释执行字节码的同时,收集代码信息,发现是热点代码之后,TurboFan 编译器把热点的字节码编译成机器码,并保存起来以供下次使用

垃圾回收

V8 的垃圾回收

栈空间

原始数据类型存放与栈空间中

函数执行上下文 -> 全局执行上下文(采用调用栈的形式记录数据)

堆空间

引用类型使用堆空间存储(栈中存储的是引用地址)

堆空间的垃圾回收

代际假说

  1. 对象一般不会存储太久,一经分配内存,很快就会变得不可访问
  2. 不死的对象活的更久

新生代

对象存放的时间较短(1-8M 的存储空间)

老生代

对象存放的时间较长

垃圾回收流程

所有垃圾回收机制,都有一套完整的统一流程

  • 标记空间中的活动对象和非活动对象
    • 活动对象 = 正在使用中的对象
    • 非活动对象 = 需要垃圾回收的对象
  • 回收非活动对象占用的空间
    • 所有标记完成之后,统一清理内存中所有被标记为可回收的对象
  • 内存整理
    • 整理内存碎片(不连续的内存空间)

副垃圾回收器

主要负责新生代区间的垃圾回收

Scanvenge 算法 -> 新生区区间划分为两个区域:对象区域、空闲区域

  • 新加入的对象会加入对象区域,对象区域存满之后,会执行一次垃圾回收操作

  • 对对象区域中的垃圾进行标记

  • 标记完毕,副垃圾回收器会对没有进行标记的对象进行复制,复制到空闲区域,同时还会排列起来(内存整理)

  • 对象区域和空闲区域角色反转(对象区域=空闲区域 空闲区域=对象区域)两块区域重复利用

对象晋升策略

为了执行效率,新生代的空间一般不会设置得太大,因此经过两次垃圾回收的活动对象依然存在,会被放入到老生代区间中

主垃圾回收器

主要负责老生代区间的垃圾回收

  • 空间大
  • 对象存活时间长

标记 - 清除(Mark - Sweep)

  • 从一组根元素开始
  • 递归遍历根元素(能访问到的元素为活动对象,不能访问到的元素为垃圾数据)

标记 - 整理(Mark - Compact)

标记清除算法会产生大量的不连续内存碎片,导致大对象无法分配一块连续空间的内存

  • 所有的活动对象都向一端移动,然后直接清理掉端边界以外的内存

全停顿

Stop-The-World

JavaScript 是运行在主线程之上,一旦执行垃圾回收算法,脚本执行需要停顿,待垃圾回收完毕之后恢复脚本的执行

增量标记

Incremental Marking

全停顿算法,一旦垃圾回收算法执行时间过长,页面会出现卡顿现象,导致用户体验下降

  • 一个完整的垃圾回收任务拆分成很小的任务
  • 小任务执行时间很短,穿插在 JavaScript 任务中

Released under the MIT License.