V8工作原理

V8工作原理

V8工作原理

JavaScript 共有8中数据类型,其中有7中原始类型(Number\String\Boolean\undefined\null\BigInt\Symbol)和1种引用类型(Object)。原始类型的赋值会完整的赋值变量值,引用类型的赋值是复制引用地址。

JavaScript 的内存模型分为三种:代码空间、栈空间、堆空间。

栈空间和堆空间

原始类型的数据存放在中,引用类型存在堆中。堆中的数据是通过引用和变量关联起来的,JavaScript 的变量没有数据类型,值才有数据类型,变量可以持有任何数据类型的数据。

image

为什么引用数据类型放在空间?因为该类型数据占用的空间往往比较大,如果放在中,会影响到调用栈的执行上下文的切换效率,也可能导致栈空间不足;而堆空间比较大,能存放很多大的数据。

垃圾回收机制

程序中,有些数据在使用之后,我们不再需要了,这种数据就称为垃圾数据。只有回收了垃圾数据,才能释放内存空间,无法回收或回收不及时的情况,我们就称为内存泄漏

JavaScript 的垃圾是由垃圾回收器自动回收的。数据存放在“栈空间”和“堆空间”,那垃圾回收器是如何回收的呢?

调用栈中的数据是如何回收的

调用栈在入栈时存储上下文数据,当某上下文执行完成出栈(pop stack) 后,就销毁掉了执行上下文,相应的内存空间就会被回收。

调用栈中通过记录当前执行状态的指针(称为 ESP)下移操作,来销毁执行完毕的函数存在栈中的执行上下文的过程。

堆中的数据是如何回收的

回收堆中的垃圾数据,如要利用 JavaScript 的垃圾回收器。

代际假说和分代收集

代际假说(The Generational Hypothesis)

  • 第一个是大部分对象在内存中存在的时间很短,就是很多对象一经过分配内存,很快就变得不可访问。
  • 第二个是不死的对象,会活得更久。

V8 中会把堆分为新生代老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。新生代区通常只支持 1~8M 的容量,老生代区则容量大很多。这两块区域,V8分别使用了不同的垃圾回收器,以便更高效的实施垃圾回收。

  • 副垃圾回收器,主要负责新生代的垃圾回收。
  • 主垃圾回收器,主要负责老生代的垃圾回收。

垃圾回收器的工作流程

  • 1、标记空间汇总活动对象和非活动对象
  • 2、回收非活动对象所占据的内存
  • 3、内存整理

副垃圾回收器使用 Scavenge 算法 结合 对象晋升策略

主垃圾回收器使用 **标记-清除(Mark-Sweep)算法进行垃圾回收,然后使用标记-整理(Mark-Compact)**进行内存整理。

全停顿

由于垃圾回收器是运行在 JavaScript 主线程上的,单线程的原因,执行垃圾回收算法的时候使得 JavaScript 脚本执行暂停,导致性能下降。为了解决这个问题,V8 将垃圾回收中的标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,知道标记阶段完成(增量标记 Incremental Marking 算法)。

编译器和解析器

编译型语言 在执行程序之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了。比如 C/C++、GO、Java等。

解释型语言 在每次运行时都需要通过解释器对程序进行动态的解释和执行。比如 Python、JavaScript。

image

V8 依据 JavaScript 代码生成 AST 和执行上下文,再基于 AST 生成字节码(编译),然后通过解释器执行字节码,通过编译器来优化编译字节码。

JavaScript 中编译面向的是全局代码或函数,比如下载完一个js文件,先编译这个js文件,但是js文件内定义的函数是不会编译的,等调用到该函数的时候,JavaScript 引擎才会去编译该函数。

可以吧 JavaScript 的编译看成部分:

  • 第一部分从一段 JavaScript 代码编译到字节码,然后解释器解释执行字节码;
  • 第二部分深度编译,将活跃的字节码编译成二进制,然后直接执行二进制。

参考资源:《浏览器的工作原理与实践》极客时间-李兵


本人自动发布于:https://github.com/giscafer/blog/issues/40

相关文章