栈空间和堆空间

JS 是什么类型的语言

  • 弱类型:支持隐式类型转换
  • 动态:可以使用同一个变量保存不同类型的数据,在运行过程中会检查数据类型
    1. function foo(){
    2. var a = "极客时间"
    3. var b = a
    4. var c = {name:"极客时间"}
    5. var d = c
    6. }
    7. foo()
    22100df5c75fb51037d7a929777c57bc.png
    d7153d003a72dbd0a9ca84b59ac3857b.png

    JavaScript 引擎需要用栈来维护程序执行期间上下文的状态,如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率。

闭包

产生闭包的核心:

  1. 预扫描内部函数
  2. 把内部函数引用的外部变量保存到堆中
    1. function foo() {
    2. var myName = "极客时间"
    3. let test1 = 1
    4. const test2 = 2
    5. var innerBar = {
    6. setName:function(newName){
    7. myName = newName
    8. },
    9. getName:function(){
    10. console.log(test1)
    11. return myName
    12. }
    13. }
    14. return innerBar
    15. }
    16. var bar = foo()
    17. bar.setName("极客邦")
    18. bar.getName()
    19. console.log(bar.getName())
    f9dd29ff5371c247e10546393c904edb.png
    当 foo 函数的执行上下文被销毁时,闭包引用的变量仍存在在堆中,所以存在内存问题

    垃圾回收

    由垃圾回收器管理

调用栈中的数据回收

当一个函数执行结束之后,JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文。 记录当前执行状态的指针(称为 ESP)

b899cb27c0d92c31f9377db59939aaf3.jpg

堆中的数据回收

由垃圾回收器控制

代际假说和分代收集

代际假说(the generational hypothesis):很多对象在内存中存在的时间很短(die young)
V8 中把堆分为新生代和老生代两个区域:

  • 新生代:生存时间短的对象,由副垃圾回收器负责垃圾回收
  • 老生代:生存时间久的对象,由主垃圾回收器负责

    垃圾回收器的工作流程

  1. 标记是否活动对象
  2. 回收非活动对象内存
  3. 内存整理

d015db8ad0df7f0ccb1bdb8e31f96e85.png
652bd2df726d0aa5e67fe8489f39a18c.png

副垃圾回收器

4f9310c7da631fa5a57f871099bfbeaf.png
Scavenge 算法:在对象区域进行垃圾回收流程后,把存活的对象复制到空闲区域,然后角色翻转,原本的对象区域成为空闲区域,空闲区域成为对象区域。

主垃圾回收器

标记 - 清除算法(Mark - Sweep)
6c8361d3e52c1c37a06699ed94652e69.png

全停顿

垃圾回收运行在主线程上,造成卡顿
9898646a08b46bce4f12f918f3c1e60c.png

增量标记算法(Incremental Marking)

V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成

de117fc96ae425ed90366e9060aa14e7.png

编译器和解释器

4e196603ecb78188e99e963e251b9781.png

V8 如何执行 JS 代码

8a34ae8c1a7a0f87e19b1384a025e354.jpg

  1. 初始化基础环境
  2. 解析源码生成 AST 和作用域
  3. 依据 AST 和作用域生成字节码
  4. 解释执行字节码
  5. 监听热点代码
  6. 优化热点代码为二进制的机器代码
  7. 反优化生成的二进制机器代码

    生成字节码

    字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行。 为了减少系统的内存使用

87d1ab147d1dc4b78488e2443d58a3ff.png

执行代码

在解释器( Ignition )执行字节码的过程中,如果发现有热点代码(HotSpot),比如一段代码被重复执行多次,这种就称为热点代码,那么后台的编译器 TurboFan 就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。

即时编译(JIT)技术

混合使用编译器和解释器的技术称为 JIT(Just In Time)技术

662413313149f66fe0880113cb6ab98a.png

参考资料

  1. 浏览器工作原理与实践
  2. 谈谈 GC:新的 Orinoco 垃圾收集器