写点什么

JavaScript 引擎分析

  • 2019-08-22
  • 本文字数:4121 字

    阅读完需:约 14 分钟

JavaScript引擎分析

一.JavaScript 简介

JavaScript 是一种动态类型的脚本语言;在 1995 年时,由 Netscape 公司的 Brendan Eich,在网景导航者浏览器上首次设计实现而成。因为 Netscape 与 Sun 合作,Netscape 管理层希望它外观看起来像 Java,因此取名为 JavaScript。


JavaScript 脚本语言具有以下特点:


(1)脚本语言。JavaScript 是一种解释型的脚本语言,是在程序的运行过程中逐行进行解释执行,不需要预编译。;而 Java、C++等语言需要先编译后执行;


(2)动态性。JavaScript 能够动态修改对象的属性,没有办法在编译的时候知道变量的类型,只有在运行的时候才能确定;而 Java、C++等都是静态类型语言,他们在编译的时候就能够知道每个变量的类型;


(3)跨平台性。JavaScript 脚本语言不依赖于操作系统,仅需要浏览器的支持。可以在多种平台下运行(如 Windows、Linux、Mac、Android、IOS 等)

二.JavaScript 与 Java 语言区别

从上面介绍的 JavaScript 语言特点会发现 JavaScript 的效率会比 Java、C++低很多;看以下这个实例:



当 JavaScript 引擎分析到该段代码的时候,根本不知道 a 和 b 是什么类型,唯一的办法就是运行的时候根据实际传过来的对象再来计算,这显然会导致严重的性能问题;



当编译上面 Java 代码的时候,根据右边类型 Class1 的定义,获取对象 a 的属性 x 的时候,其实就是对象 a 的地址,大小是一个整形。同时获取对象 b 的属性 y 的时候,其实就是对象 b 的地址加上 4 个字节,这些都是在生成本地代码的时候确定的,无需再运行本地代码的时候再决定他们的地址和类型是什么,这显然能够节省时间;


再看一下两者分别是怎样存储对象 a 和 b 的:


对于传统的 JavaScript 解释器来说,因为不知道 a 和 b 的具体类型,就用属性名-属性值对来保存,之后访问对象的属性值时就需要通过属性名匹配来获取对应的值;对象 b 也是同样的结果来保存相同的属性;随着对象的增多,这显然带来了巨大的空间浪费;



而上面的 Java 代码在编译时就确定了类 Class1 的成员类型,访问 x 就是对象 a 的地址,y 就是 a 的地址加上 4 个字节;所以字符“x”和“y”运行时都不在需要;因为不再需要额外的查找这些属性地址的工作;



从上面实例可以看到 JavaScript 和 Java 语言区别包括以下几个部分:


编译确定位置: Java 有编译和执行两个阶段,位置的偏移信息都是在编译器编译的时候决定的,当 java 生成本地代码之后,对象的属性和偏移信息都计算完成;而 JavaScript 没有类型,只有在对象执行创建的时候才确定这些信息,而且 JavaScript 语言能够在执行的时候修改对象的属性。


偏移信息共享: Java 有类型定义,所有的对象都是共享偏移信息的;访问他们只需要按照编译时确定的偏移量即可。JavaScript 则不同,每个对象都有自我描述,属性和位置偏移信息都包含在自身的结构中。


偏移信息查找: Java 查找偏移地址很简单,都是在编译代码时,对使用到的类型成员变量直接设置偏移量;而 JavaScript 则需要通过属性名匹配才能查找到对应的值。


Java 语言有明显的两个阶段:编译和运行,如下图所示:



Java 代码经过编译器编译之后生成的是字节码,字节码是跨平台的一种中间表示,不同于本地代码。该字节码于平台无关,能够在不同的操作系统上运行。在运行字节码阶段,Java 的运行环境是 Java 虚拟机加载字节码。Java 虚拟机一般都引入 JIT 技术来将字节码转变成本地代码来提高执行效率。第一阶段对时间要求不严格,第二阶段对每个步骤所花费的时间非常敏感,时间越短越好。


JavaScript 语言的编译和执行都是在运行阶段执行的,如下图所示:



因为都是在代码运行过程中来处理这些步骤,所以每个阶段的时间越短越好,而且每引入一个阶段都是额外的时间开销;所以一个 JavaScript 引起主要包含以下几个部分:


编译器: 主要工作是将源代码编译成抽象语法树;


解释器: 主要是接受字节码,解释执行这个字节码;


JIT 工具: 将字节码或抽象语法树转换成本地代码;


垃圾回收期和分析工具(Profiler): 负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能;

三.V8 引擎介绍

V8 是一个 JavaScript 引擎实现的开源项目,最开始由一些语言学家设计出来,后被 Google 收购,成为了 JavaScript 引擎和众多相关技术的引领者。V8 支持众多的操作系统,包括 Windows、Linux、Android、Mac OS X 等;同时它也能够支持众多的硬件架构 IA32、X64、ARM、MIPS 等,他将主流软硬件平台一网打尽,由于它是一个开源项目,开发者可以自由使用它的强大能力,目前炙手可热的 NodeJs 项目就是基于 V8 项目研发的。

1.调用 V8 编程接口的例子和对应的内存管理方式:


第一条语句:表示建立一个域,用于包含一组 Handle 对象,便于管理和释放他们;


第二条语句:根据 Isolate 对象来获取一个 Context 对象,使用 Handle 来管理。Handle 对象本身存放在栈上,而实际的 Context 对象保存在堆中。


第三条语句:根据两个对象 Isolate 和 Context 来创建一个函数间使用的对象,使用 Persistent 类来管理;


第四条语句:表示为 Context 对象创建一个基于栈的域,下面的执行步骤都是在该域中对应的上下文中来进行的;


第五条语句:读入一段 JavaScript 代码;


第六条语句:将代码字符串编译成 V8 的内部表示,并保存成一个 Script 对象;


第七条语句:执行编译后的内部表示,获得生成的结果;

2.V8 的编译:


首先通过编译器将源代码编译成抽象语法树,不同于 JavaScriptCore 引擎,V8 引擎并不将抽象语法树转变成字节码,而是通过 JIT 编译器的全代码生成器从抽象语法树直接生成本地代码;


其过程中的主要类图如下:



Script:表示的是 JavaScript 代码,既包含源代码,又包含编译之后生成的本地代码,所以它既是编译入口,又是运行入口;


Compiter:编译器类,辅助 Script 类来编译生成代码,它主要起一个协调者的作用,会调用解析器(Parse)来生成抽象语法树和全代码生成器,来为抽象语法树生成本地代码;


Parse:将源代码解析并构建成抽象语法树,使用 AstNodeFactory 类来创建他们,并使用 Zone 类来分配内存;


AstNode:抽象语法树节点类,是其他所有节点的基类;


AstVisitor:抽象语法树的访问者类,主要用来遍历抽象语法树;


FullCodeGenerator:AstVisitor 类的子类,通过遍历抽象语法树来为 JavaScript 生成本地可执行的代码;

3.V8 运行

V8 运行阶段的主要类图如下:



Script:前面介绍过,包含编译之后生成的本地代码,运行代码的入口;


Execution:运行代码的辅助类,包含一些重要的函数“call”,它辅助进入和执行 Script 中的本地代码;


JSFunction:需要执行的 JavaScript 函数表示类;


Runtime:运行本地代码的辅助类,主要提供运行时各种辅助函数;


Heap:运行本地代码需要使用的内存堆;


MarkCompactCollector:垃圾回收机制的主要实现类,用来标记,清除和整理等基本的垃圾回收过程;


SweeperThread:负责垃圾回收的线程;


V8 中代码的执行过程如下图:


四.V8 引擎所做优化

1.优化回滚

Crankshaft 编译器主要针对热点函数进行优化,它是基于 JS 源码分析的,而不是本地代码。为了性能考虑 Crankshaft 编译器会进行一些乐观的预测,认为这些代码比较稳定,变量类型不会发生变化,所以能够生成高效的本地代码;然而进行优化之后,V8 发现并不是最优的,会执行优化回滚操作。

2.隐藏类

将对象划分成不同的组,相同的组内对象拥有相同的属性名和属性值,组内的所有对象贡献该信息;



实例中对象 a 和 b 包含相同的属性名,V8 就会把他们归为同一个组,也就是隐藏类;这些属性在隐藏类中有相同的偏移值,这样,对象 a 和 b 可以共享这个类型信息,当访问这些对象属性的时候,根据隐藏类的偏移值就可以知道他们的位置并进行访问。


###3.内存管理


V8 使用堆来管理 JavaScript 使用的数据,以及生成的代码,哈希表等;为了更方便的实现垃圾回收,同很多虚拟机一样,V8 将堆分成三个部分,第一个是年轻分代,第二个是年老分代,第三个是大对象保留的空间。如下图:


4.快照(Snapshot)

V8 引擎开始启动的时候,需要加载很多内置的全局对象,同时也要建立内置的函数,比如 Array、String、Math 等;为了让引擎更加整洁,加载对象与建立函数等任务都是使用 JS 文件来实现的,V8 引擎负责在编译和执行输入的 JavaScript 代码之前,先加载他们;


快照机制就是将一些内置的对象和函数加载之后的内存保存并序列化;序列化之后的结果很容易被发序列化,经过快照机制的启动时间,可以缩短启动时间;快照机制也能够将开发者认为需要的 JS 文件序列化,减少以后处理的时间;

5.绑定和扩展

V8 提供两种机制来扩展引擎的能力,第一是 Extension 机制,就是通过 V8 提供的基类 Extension 来达到扩展 JavaScript 能力的目的;第二是绑定,使用 IDL 文件或者接口文件来生成绑定文件,然后将这些文件同 V8 引擎代码一起编译。

五.实践 – 写 JavaScript 需要注意地方

1.不要破坏隐藏类


建议:在构造函数中初始化所有对象成员,不要在以后更改类型;以相同的顺序初始化对象成员。

2.数据表示

在 V8 中,数据的表示分成两个部分,第一个部分是数据的实际内容,他们是变长的,第二部分是数据的句柄,句柄的大小是固定的,句柄中包含指向数据的指针。为什么要这样设计呢?主要是因为 V8 需要进行垃圾回收,并需要移动这些数据内容,如果直接使用指针的话就会出问题或者需要比较大的开销,使用句柄的话就不存在这些问题,只需要将句柄中的指针修改即可。


具体的定义如下:



一个 Handler 的大小是 4 字节(32 位机器),整数直接从 value_中获取值,而无需从堆中分配,然后分配一个指针指向它,这可以减少内存的使用并增加数据的访问速度。


所以:对于数值来说,只要能够使用整数的,尽量不要使用浮点数。

3.数组初始化


建议:


初始化使用数组常量小型固定大小的数组


不要储存在数字数组非数字值(对象)


不要删除数组中的元素,尤其是数字数组


不要装入未初始化或删除元素

4.内存

对引用不再使用的对象的变量设置为空(a = null),引入 delete 关键字,删除无用对象。

5.优化回滚

不要书写出触发优化回滚的代码,否则会大幅降低代码的性能;执行多次之后,不要出现修改对象类型的语句。


本文转载自公众号小时光茶舍(ID:gh_7322a0f167b5)。


原文链接:


https://mp.weixin.qq.com/s/nV75KBmRGpcE7Pn5Z-HUFg


2019-08-22 10:502282

评论

发布
暂无评论
发现更多内容

一文带你了解什么是GitOps

华为云开发者联盟

DevOps 运维 测试 软件开发 gitops

华为超大云数据中心落地贵州,这些硬核技术有利支撑“东数西算”

华为云开发者联盟

服务器 数据中心 华为云 东数西算 云数据中心

毕业10年才懂,会升层思考,工作有多轻松?

阿里技术

技术管理 技术人生 内容合集

7 个建议让 Code Review 高效又高质

阿里技术

技术管理 技术人生 内容合集

在高并发环境下该如何构建应用级缓存

华为云开发者联盟

缓存 高并发 负载 应用级缓存 缓存命中率

自用学习资料,Linux内核之【内存管理】的一些分享

奔着腾讯去

内存泄露 C/C++ Linux内核 内存映射 内存池

互联网时代,谁来保护我们的个人隐私信息?

郑州埃文科技

数据库 App IP 个人信息

架构实战-模块七-作业

无名

架构实战营 「架构实战营」

为企业创建完美CRM系统策略

低代码小观

企业管理 CRM 客户关系管理 CRM系统 客户关系管理系统

OPPO大数据离线任务调度系统OFLOW

安第斯智能云

后端 数据

Typora + picGo实现插入图片上传gitee图床

zdd

一周信创舆情观察(12.13~12.19)

统小信uos

上百台linux服务器管理用什么软件好?谁给推荐一下!

行云管家

Linux 服务器 服务器管理

拍乐云发布“融合语音通话”产品,实现多场景下VoIP和PSTN互通

拍乐云Pano

RTC PSTN VoIP 融合语音通话

如何成为优秀的技术主管?你要做到这三点

阿里技术

技术管理 技术人生 内容合集

MySQL从入门到入魔之数据库连接池(04)

海拥(haiyong.site)

MySQL 数据库 28天写作 12月日更

短视频如何有效去重?vivo 短视频分享去重实践

Zilliz

数据库 Milvus Zilliz

Linux环境变量配置

恒生LIGHT云社区

Linux 运维 环境配置 环境变量

CSS之变量(四)悬浮跟踪按钮

Augus

CSS 12月日更

如何做好技术 Team Leader?

阿里技术

技术管理 技术人 内容合集

为什么大部分人做不了架构师?这2点是关键

阿里技术

技术人生 内容合集

优秀工程师必备的一项技能,你解锁了吗?

阿里技术

技术管理 技术人生 内容合集

在阿里做了五年技术主管,我有话想说

阿里技术

技术人生 内容合集

Linux云计算有那么难学吗?Linux入门篇。系统常用函数的调用方法大全

学神来啦

MySQL nginx Linux Shell linux云计算

【浅谈黑客与学习思路】黑客的种类和行为,初学者应该怎样学习

H

黑客 网络安全 信息安全

在阿里,我如何做好技术项目管理?

阿里技术

技术管理 技术人生 内容合集

Python代码阅读(第71篇):检测一个平坦列表中是否有重复元素

Felix

Python List 编程 阅读代码 Python初学者

如何提高一个研发团队的“代码速度”?

阿里技术

技术管理 技术人生 内容合集

面对复杂业务,if-else coder 如何升级?

阿里技术

技术人生 内容合集

EasyRecovery的高级设置如何使用

淋雨

数据恢复 EasyRecovery

RPA的定义

金小K

RPA 自动化 自动化平台 自动化运维

JavaScript引擎分析_文化 & 方法_厉心刚_InfoQ精选文章