QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

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:502321

评论

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

硬核!阿里最新出品架构核心场景实战手册,解决99%的架构问题

Java你猿哥

微服务 架构设计 架构师 架构场景实战 微服务实战

从浏览器输入域名开始分析DNS解析过程

华为云开发者联盟

开发 华为云 DNS 华为云开发者联盟 企业号 5 月 PK 榜

AI 大底座,大模型时代的答卷

百度Geek说

人工智能 百度 文心一言 企业号 5 月 PK 榜

阿里官方上线!号称Java面试八股文天花板(2023最新版)首次开源

Java你猿哥

Java redis Spring Boot JVM java面试

开源之夏 2023 | 与 Databend 一同探索云数仓的魅力

Databend

来了!昇腾MindStudio全流程工具链分论坛精彩回顾,助力高效开发和迁移效率提升

科技热闻

Java并发编程必备:分布式锁的选型和性能对比

做梦都在改BUG

Java 数据库 分布式锁

零信任是什么意思?与堡垒机有什么区别?

行云管家

网络安全 堡垒机 零信任

构建万物互联,华为云IoT+鸿蒙重燃物体感知

华为云开发者联盟

鸿蒙 物联网 华为云 华为云开发者联盟 企业号 5 月 PK 榜

用户分享 | Dockquery,一个国产数据库客户端的初体验

BinTools图尔兹

用户体验 国产数据库工具

Webpack5构建性能优化:构建耗时从150s到60s再到10s | 京东云技术团队

京东科技开发者

性能优化 webpack webpack-tapable 企业号 5 月 PK 榜

sysMaster: 全新1号进程实现方案,秒级自愈,保障系统全天在线

openEuler

Linux rust 操作系统 openEuler init

硬核Prompt赏析:HuggingGPT告诉你Prompt可以有多“工程”

无人之路

ChatGPT Prompt

震撼来袭!最具中国特色的微服务组件:新一代SpringCloud Alibaba

做梦都在改BUG

Java 架构 微服务 Spring Cloud spring cloud alibaba

阿里全新推出:微服务突击手册,把所有操作都写出来了|超清PDF

做梦都在改BUG

Java 架构 微服务 Spring Cloud spring cloud alibaba

云数据库技术沙龙|多云多源下的数据复制技术解读-NineData

NineData

MySQL Clickhouse 数据管理 多云多源 数据存取

精选!字节大佬带你一周刷完Java面试八股文,比啃书效果好多了

Java你猿哥

Java 算法 ssm java面试 java知识点

重磅发布!阿里巴巴专家亲自撰写,Dubbo 3.0 分布式实战(彩印版)

做梦都在改BUG

Java 分布式 微服务 dubbo

鲸鸿动能广告接入如何高效变现流量?

HarmonyOS SDK

HMS Core

一路同行,端点科技与海尔集团相伴十年的数字之旅

科技热闻

k8s+Docker部署方法

Java你猿哥

Java Docker k8s ssm 架构师

MySQL百万数据深度分页优化思路分析

Java你猿哥

Java MySQL 数据库 ssm 优化技术

牛掰!阿里架构师熬夜肝了一份JVM必知必会,哪里不会查哪里

做梦都在改BUG

Java 性能优化 JVM

如何选择最优权限框架?Sa-Token 和 Shiro 对比

做梦都在改BUG

shiro Sa-Token

一周吃透Java面试八股文(2023最新整理)

Java你猿哥

Java kafka Spring Boot JVM java面试

本周日直播,全链路数据治理实践论坛开放报名

阿里云大数据AI技术

大数据 数据治理

Redis和MySQL扛不住,B站分布式存储系统如何演进?

Java你猿哥

Java MySQL redis ssm kv

基于图神经网络的推荐算法

TiAmo

神经网络 算法 推荐算法

责任链模式在复杂数据处理场景中的实战

阿里技术

设计模式 技术实践

独家巨献!阿里专家兼Github贡献者,整理的SpringBoot入门到成神

做梦都在改BUG

Java spring 架构 微服务 Spring Boot

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