js引擎
为什么需要js引擎
高级编程语言都要转化为最终的机械指令来执行
我们平时编写的js,无论交给浏览器或Node执行,最终都需要被CPU执行
CPU只认识自己的指令集(机械语言只包含0和1)能被CPU执行
需要js引擎将js代码翻译成CPU指令来执行
常见js引擎
SpiderMonkey:第一款js引擎,js作者开发
Chakra:IE浏览器,微软开发
JavaScriptCore:WebKit,Apple公司开发
V8:Google,Google开发
垃圾回收机制可看
https://zhuanlan.zhihu.com/p/390563834
https://www.cnblogs.com/12345huangchun/p/10217342.html
V8工作原理
https://blog.csdn.net/qq_45644850/article/details/118895975
https://blog.csdn.net/qq_44482048/article/details/124692787
V8如何垃圾回收
https://blog.csdn.net/qq_53225741/article/details/125330813
从栈中回收
js引擎通过向下移动ESP销毁该函数保存在栈中的执行上下文
showName函数执行结束后,ESP向下移动到foo函数的执行上下文中,上面showName的执行上下文虽然保存在栈内存中,但已经为无效内存了,如当foo函数再次调用另外一个函数时,该内容会直接覆盖掉,用来存放另一个函数的执行上下文。
从堆中回收
V8中会把堆分为两个区域
新生代(存放生存时间短的对象)通常只支持1-8m的容量
老生代(存放生存时间久的对象)支持容量比新生代大很多
V8分别使用两个不同垃圾回收器
副垃圾回收器:主要负责新生代的垃圾回收
主垃圾回收器:主要负责老生代的垃圾回收。
不论什么类型的垃圾回收器,都有一套共同的执行流程:
- 标记空间中活动对象(还在使用的对象)和非活动对象(可进行垃圾回收的对象)
- 回收非活动对象所占据的内存。即在完成所有标记后,统一清理内存中所有被标记为可回收的对象
- 内存整理。频繁回收对象后,内存中会存在大量不连续空间,即内存碎片。内存中出现大量内存碎片时,可能会导致后期分配内存时出现内存不足情况。
注意:副垃圾回收器不会产生内存碎片
新生代(生存时间短)副垃圾回收器
算法:Scavenge算法
原理:
- 把新生代空间对半划分为两个区域,对象区域,空闲区域
- 新加入的对象存放在对象区域,对象区域快被写满时,需要执行一次垃圾清理操作
- 先对对象区域中的垃圾做标记,标记完成后,把这些存活的对象 复制到空闲区域中
- 完成复制后,对象区域和空间区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成对象区域
为什么新生代的空间会被设置得比较小?
由于新生代中采用的 Scavenge 算法,所以每次执行清理操作时,都需要将存活的对象从对象区域复制到空闲区域。
但复制操作需要时间成本,如果新生区空间设置得太大,会导致每次清理的时间就会过久,所以为了执行效率,一般新生区的空间会被设置得比较小。
也正是因为新生区的空间不大,所以很容易被存活的对象装满整个区域。为了解决这个问题,JavaScript 引擎采用对象晋升策略
对象晋升策略:经过两次垃圾回收依然存活的对象,会移动到老生代区中
老生代(生存时间久)主垃圾回收器
算法:标记 - 清除(Mark-Sweep)算法
原理:
- 标记:标记阶段从一组根元素开始,递归遍历这组元素,在这个遍历过程中,能到达的元素称为活动元素(如某块数据被一个变量b引用了,那么这款数据会被标记为活动对象)没有到达的元素可判断为垃圾数据
- 清除:清除垃圾数据
碎片:对一块内存多次执行标记 - 清除算法后,会产生大量不连续的内存碎片。碎片过多导致大对象无法分配到足够的连续内存
算法:标记 - 整理(Mark-Compact)算法
标记过程和标记 - 清除算法一样。后续步骤不是直接对可回收对象进行清理,是让所有存活的对象都向一端移动,然后直接清理调端边界以外的内存
优化算法:增量标记(Incremental Marking)算法
原理:
- 为降低老生代的垃圾回收造成的卡顿
- V8把一个完整的垃圾回收任务拆分为很多小的任务
- 让垃圾回收标记和JavaScript应用逻辑交替进行直到标记阶段完成
全停顿
V8使用主副垃圾回收器进行垃圾回收,由于js是运行在主线程之上,一旦执行垃圾回收算法,最终都需将正在执行的JavaScript脚本暂停,待垃圾回收完毕后再恢复脚本执行
V8如何执行一段JavaScript代码
V8执行一段代码流程
生成抽象语法树(AST)和执行上下文
执行上下文主要是代码在执行过程中的环境信息,AST可看成代码结构化的表示。AST的生成过程,先分词(词法分析)再解析(语法分析)
AST是一种非常重要的数据结构,有广泛的应用。
babel(将es6转为es5)工作原理:先将es6源码转换为AST,将es6语法的AST转为es5语法的AST,最后利用es5的AST生成JavaScript源代码。
eslint(检查JavaScript编写规范的插件)检测流程:需要将源码转化为AST,利用AST检查代码规范化的问题
生成字节码(解释器)
字节码就是介于AST和机器码之间的一种代码,与特定类型的机器码无关,字节码需要通过解释器将其转换为机器码后才能执行
执行代码
解释器lgnition除了负责生成字节码外,还有另一个作用,解释执行字节码。如果有一段第一次执行的字节码,解释器lgnition会逐条解释执行,在lgnition执行字节码过程中,如果发现有热点代码(HotSpot)如一段代码被重复执行多次,这种就时热点代码,那么后台的编译器TurboFan会把该段热点的字节码编译为高效的机器码,当再次执行这段被优化的代码时,只需执行编译后的机器码即可。提升代码执行效率
补充:Ignition->点火器,TurboFan->螺旋增压,寓意着代码启动时通过点火器慢慢发动,一旦启动,涡轮增压介入,其执行效率随着执行时间越来越高。
为什么转为字节码不是机器码
js执行的环境不固定,可能是Windows,Mac,Linux,Node.js,不同环境中有不同CPU,不同CPU有不同CPU架构,不同架构能执行的机器指令不一样
即时编译技术JIT