阅读 2508

JavaScript-V8引擎

一、浏览器内核-渲染引擎

渲染就是根据描述或者定义构建一个数据模型,生成图形的过程。

浏览器内核就是将页面(Html、Css、JavaScript)构建成可视化、可听化的多媒体结果。

我们也可以将浏览器内核称之为"渲染引擎",渲染引擎经常做的事情就是将Html、Css、JavaScript文本或者其他的资源文件转换成我们浏览器网页。

二、JavaScript引擎

2.1JavaScript引擎(JavaScript Engine)

当我们在执行一段代码时,真正赋予这段代码生命的就是JavaScript引擎(JavaScript Engine)。JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器之中。

JavaScript引擎有许多种:

  • V8 — 开源,由 Google 开发,用 C ++ 编写。
  • Rhino — 由 Mozilla 基金会管理,开源,完全用 Java 开发。
  • SpiderMonkey — 是第一个支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用。
  • JavaScriptCore — 开源,以Nitro形式销售,由苹果为Safari开发。
  • KJS — KDE 的引擎,最初由 Harri Porten 为 KDE 项目中的 Konqueror 网页浏览器开发。
  • Chakra (JScript9) — Internet Explorer。
  • Chakra (JavaScript) — Microsoft Edge。
  • Nashorn, 作为 OpenJDK 的一部分,由 Oracle Java 语言和工具组编写。
  • JerryScript —  物联网的轻量级引擎。

而最为大家熟知的无疑是V8引擎,他用于Chrome浏览器和Node中。

JavaScript引擎从头到尾负责整个JavaScript程序的编译和执行过程。

2.2渲染引擎和JavaScript引擎的关系

  • 渲染引擎通过调用接口来处理JavaScript的逻辑。
  • JavaScript引擎通过桥接接口来访问渲染引擎的DOM、CSSDOM等。

2.3JavaScript引擎如何工作?

JavaScript引擎主要组成部分:

  • 编译器:负责语法分析和代码生成。
  • 解析器:负责接收字码节和解析执行字码节。
  • JIT工具:将字码节或者抽象语法树转换为本地代码(可执行代码)。
  • 垃圾回收器和分析工具(Profiler):负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能和功效。

JavaScript本质上是一种解释型语言,与编译型语言不同的是它需要一遍执行一边解析,而编译型语言在执行时已经完成编译,可直接执行,有更快的执行速度。

三、JavaScript-V8引擎的编译和执行

3.1数据表示

  • 基本数据类型:Boolean、Number、String、Null、Undefined、Symbol、BigInt(起草第二阶段,但是以后在JavaScript中使用时板上钉钉的事情了)
  • 对象类型(Array、Object、Date、Error等等)。 在V8中,数据表示分为两部分:
  1. 数据的实际内容,它们是变长的,而且内容的类型也不一样。
  2. 数据的句柄,大小是固定的,包含指向第一部分数据的指针。

3.2句柄Handle

V8需要进行垃圾回收,并需要移动这些数据内容,如果直接使用指针的话就会出问题或者需要比较大的开销。使用句柄就不存在这些问题,只需要修改句柄中的指针即可,使用者使用的还是句柄,它本身没有发生变化。

3.3内存堆和调用栈

  • mory Heap(内存堆) — 内存分配地址的地方
  • Call Stack(调用栈) — 代码执行的地方

3.3编译执行过程

编译和执行

四、V8内存分配

4.1小内存区块Zone类

管理一系列的小块内存,这些小内存的生命周期类似,可以使用一个Zone对象。 Zone对象先对自己申请一块内存,然后管理和分配一些小内存。当一块小内存被分配之后,不能被Zone回收,只能一次性回收Zone分配的所有小内存。例如:抽象语法树的内存分配和使用,在构建之后,会生成本地代码,然后其内存被一次性全部收回,效率非常高。

  但是有一个严重的缺陷,当一个过程需要很多内存,Zone将需要分配大量的内存,却又不能及时回收,会导致内存不足情况。   

4.2堆内存

V8使用堆来管理JavaScript使用的数据、以及生成的代码、哈希表等。为了更方便地实现垃圾回收,同很多虚拟机一样,V8将堆分成三个部分。年轻代、年老代、和大对象。

五、V8的垃圾回收机制

5.1新生代Scavenge(清除)算法

主要采用Cheney(人名)算法。一种采用复制的方式实现的垃圾回收算法。   

1、将新生代堆内存分一为二,每一部分空间称为semispace。其中一个处于使用之中的称为from空间,另一个处于闲置称为to空间。

2、当我们分配对象时,先是在From空间中进行分配。

3、垃圾回收时,检查from空间内的存活对象,一是否经历过清除回收,二to空间是否已经使用了25%(保证新分配有足够的空间)。 

4、将这些存活对象复制到to空间中。非存活对象占用的空间将会被释放。

5、完成复制后,from空间与to空间角色发生对换。

   注:实际使用的堆内存是新生代中的两个semispace空间大小,和老生代所用内存大小之和。

  如何判断对象是否存活呢?作用域?是一套存储和查询变量的规则。这套规则决定了内存里对象能否访问。

  特点:清除算法是典型的牺牲空间换取时间的算法,无法大规模地应用到所有回收中,却非常适合应用在新生代生命周期短的变量。

5.2Mark-Sweep老生代标记清除

1、 标记阶段遍历堆中的所有对象,并标记活着的对象

2、 清除阶段,只清除没有被标记的对象。

  最大的问题是,在进行一次标记清除之后会出现不连续的状态。这种内存碎片会对后续的内存分配造成问题。很可能需要分配一个大对象时,所有的碎片空间都无法完成,就会提前触发垃圾回收,而这次全量回收是不必要的。

5.3Mark-Compact老生代标记整理

  在标记清除的基础上发展而来,在整理的过程中

1、 将活着的对象往一段移动

2、 移动完成后,直接清理掉边界外的内存

5.4Incremental Marking增量标记

  垃圾回收的过程都需要将应用逻辑暂停下来。为了降低全量回收带来的停顿时间,在标记阶段,将原本一口气要完成的动作改为增量标记。垃圾回收与应用逻辑交替执行到标记阶段完成。最大停顿时间较少的1/6左右.

  后续还引入了延迟清理与增量整理,让清理和整理动作也变成增量式的。

六、Javascript的V8引擎为什么快

  • 针对上下文的Snapshot技术

什么是上下文(Contexts)?实际是JS应用程序的运行环境,避免应用程序的修改相互影响,例如一个页面js修改内置对象方法toString,不应该影响到另外页面。chrome浏览器每个process只有一个V8引擎实例,浏览器中的每个窗口、iframe都对应一个上下文。

  • Built-in的js代码

利用JS自表达内置对象、方法,如上面代码实现Math.min方法,从而V8在实现代码转译时只需注重基本操作,以%符号开头的函数来自V8运行时函数。

  • 建立AST(Abstract SyntaxTree)时内存的管理。

V8在建立AST后,对其进行汇编生成动态机器语言,所以AST在code generated后需要回收;针对AST建立过程中多结点内存申请和一次性回收的特点,V8使用了内存段链表管理,并结合scopelock模式,实现少数申请(Segment,8KB~1MB)、多次分配AST结点、一次回收各个Segment的管理方式,既能避免内存碎片,又可以避免遍历AST结点逐个回收内存

  • CompileCache避免相同代码重复编译

对于一段JS代码,在开始进行词法分析前,会从编译缓存区CompilationCache查找该段代码是否已经被编译过,如果是,则直接取出编译过的机器代码,并返回,这样降低CPU的使用率,换来内存空间一定的占用;如果一个页面中重复加载JS文件,这方法的提速是很明显的;这种做法应该有平衡对比过。

  • 属性的快速访问

V8没有像其它JS Engine使用词典结构或红黑树实现的map来管理属性,而是在每个对象附加一个指针,指向hidden class(如果第一次创建该类型对象,则新建hidden class);当对象每添加一个属性时,将新建一个class(记录了每个属性的位移/位置),而原来的class指向新class,即建立起一个hidden class的转换链表。

  • Heap堆内存管理
  • Inline caching减少函数调用开销

函数绑定发生在运行时,所以无法通过method tables定位函数入口;通过该技术可以记录函数入口,避免重复查找。

  • 一次性编译生成机器语言

一般JS engine会在AST生成后,将之编译为中间语言(bytecode),在执行时候再解析这些bytecode;Java 也同样编译为这些bytecode,再采用VM(实现跨平台)作为解释器,为了提高效能,Java采用混杂方式,把无关平台、常用的代码编译为机器代码。V8则是一次性把AST编译为机器语言。从assembler相关文件头的Copyright可以看出,这些不同平台(ia32, arm)下的编译器,原型来自Sun Microsystems。

参考:juejin.im/post/684490…

参考:www.cxymsg.com/guide/mecha…

参考:blog.csdn.net/xinghuowuzh…