今天就来一起看看 V8 引擎执行JavaScript的过程和垃圾回收的机制。
在说V8的执行JavaScript代码的机制之前,我们先来看看编译型和解释型语言的区别。
我们知道,机器是不能直接理解代码的。所以,在执行程序之前,需要将代码翻译成机器能读懂的机器语言。按语言的执行流程,可以把计算机语言划分为编译型语言和解释型语言:
Java 和 C++ 等语言都是编译型语言,而 JavaScript 是解释性语言,它整体的执行速度会略慢于编译型的语言。V8 是众多浏览器的 JS 引擎中性能表现最好的一个,并且它是 Chrome 的内核,Node.js 也是基于 V8 引擎研发的。
编译型语言和解释器语言代码执行的具体流程如下:
两者的执行流程如下:
V8 在执行过程用到了解释器和编译器。 其执行过程如下:
这里前三个步骤是JavaScript的执行过程,最后一步是垃圾回收的过程。下面就先来看看V8 执行 JavaScript的过程。
这个过程就是将源代码转换为抽象语法树(AST),并生成执行上下文,执行上下文就是代码在执行过程中的环境信息。
将 JS 代码解析成 AST主要分为两个阶段:
通过词法分析会对代码逐个字符进行解析,生成类似下面结构的令牌(Token),这些令牌类型各不相同,有关键字、标识符、符号、数字等。代码 var a = 1;会转化为下面这样的令牌:
Keyword(var)
Identifier(name)
Punctuator(=)
Number(1)
语法分析阶段会用令牌生成一棵抽象语法树,生成树的过程中会去除不必要的符号令牌,然后按照语法规则来生成。下面来看两段代码:
// 第一段代码
var a = 1;
// 第二段代码
function sum (a,b) {
return a + b;
}
将这两段代码分别转换成 AST 抽象语法树之后返回的 JSON 如下:
{
"type": "Program",
"start": 0,
"end": 10,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 10,
"declarations": [
{
"type": "VariableDeclarator",
"start": 4,
"end": 9,
"id": {
"type": "Identifier",
"start": 4,
"end": 5,
"name": "a"
},
"init": {
"type": "Literal",
"start": 8,
"end": 9,
"value": 1,
"raw": "1"
}
}
],
"kind": "var"
}
],
"sourceType": "module"
}
它的样子大致如下:
V8-第 4 页.png
{
"type": "Program",
"start": 0,
"end": 38,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 38,
"id": {
"type": "Identifier",
"start": 9,
"end": 12,
"name": "sum"
},
"expression": false,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 14,
"end": 15,
"name": "a"
},
{
"type": "Identifier",
"start": 16,
"end": 17,
"name": "b"
}
],
"body": {
"type": "BlockStatement",
"start": 19,
"end": 38,
"body": [
{
"type": "ReturnStatement",
"start": 23,
"end": 36,
"argument": {
"type": "BinaryExpression",
"start": 30,
"end": 35,
"left": {
"type": "Identifier",
"start": 30,
"end": 31,
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 34,
"end": 35,
"name": "b"
}
}
}
]
}
}
],
"sourceType": "module"
}
它的样子大致如下:
V8-第 3 页.png
可以看到,AST 只是源代码语法结构的一种抽象的表示形式,计算机也不会去直接去识别 JS 代码,转换成抽象语法树也只是识别这一过程中的第一步。AST 的结构和代码的结构非常相似,其实也可以把 AST 看成代码的结构化的表示,编译器或者解释器后续的工作都需要依赖于 AST。
AST的应用场景:
AST 是一种很重要的数据结构,很多地方用到了AST。比如在 Babel 中,Babel 是一个代码转码器,可以将 ES6 代码转为 ES5 代码。Babel 的工作原理就是先将 ES6 源码转换为 AST,然后再将 ES6 语法的 AST 转换为 ES5 语法的 AST,最后利用 ES5 的 AST 生成 JavaScript 源代码。
除了 Babel 之外,ESLint 也使用到了 AST。ESLint 是一个用来检查 JavaScript 编写规范的插件,其检测流程也是需要将源码转换为 AST,然后再利用 AST 来检查代码规范化的问题。
除了上述应用场景,AST 的应用场景还有很多:
有了 抽象语法树 AST 和执行上下文后,就轮到解释器就登场了,它会根据 AST 生成字节码,并解释执行字节码。
在 V8 的早期版本中,是通过 AST 直接转换成机器码的。将 AST 直接转换为机器码会存在一些问题:
为了解决内存占用问题,就在 V8 引擎中引入了字节码。那什么是字节码呢?为什么引入字节码就能解决内存占用问题呢?
字节码就是介于 AST 和机器码之间的一种代码。 需要将其转换成机器码后才能执行,字节码是对机器码的一个抽象描述,相对于机器码而言,它的代码量更小,从而可以减少内存消耗。解释器除了可以快速生成没有优化的字节码外,还可以执行部分字节码。
生成字节码之后,就进入执行阶段了,实际上,这一步就是将字节码生成机器码。
一般情况下,如果字节码是第一次执行,那么解释器就会逐条解释执行。在执行字节码过程中,如果发现有热代码(重复执行的代码,运行次数超过某个阈值就被标记为热代码),那么后台的编译器就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码即可,这样提升了代码的执行效率。
字节码配合解释器和编译器的技术就是 即时编译(JIT)。在 V8 中就是指解释器在解释执行字节码的同时,收集代码信息,当它发现某一部分代码变热了之后,编译器便闪亮登场,把热点的字节码转换为机器码,并把转换后的机器码保存起来,以备下次使用。
因为 V8 引擎是多线程的,编译器的编译线程和生成字节码不会在同一个线程上,这样可以和解释器相互配合着使用,不受另一方的影响。下面是JIT技术的工作机制:
V8-机器码.png
解释器在得到 AST 之后,会按需进行解释和执行。也就是说如果某个函数没有被调用,则不会去解释执行它。在这个过程中解释器会将一些重复可优化的操作收集起来生成分析数据,然后将生成的字节码和分析数据传给编译器,编译器会依据分析数据来生成高度优化的机器码。
优化后的机器码的作用和缓存很类似,当解释器再次遇到相同的内容时,就可以直接执行优化后的机器码。当然优化后的代码有时可能会无法运行(比如函数参数类型改变),那么会再次反优化为字节码交给解释器。
整个过程如下图所示:
V8-全流程.png
如果JavaScript代码在执行前都要完全经过解析才能执行,那可能会面临以下问题:
所以,V8 引擎使用了延迟解析:在解析过程中,对于不是立即执行的函数,只进行预解析;只有当函数调用时,才对函数进行全量解析。
进行预解析时,只验证函数语法是否有效、解析函数声明、确定函数作用域,不生成 AST,而实现预解析的,就是 Pre-Parser 解析器。
以下面代码为例:
function sum(a, b) {
return a + b;
}
const a = 666;
const c = 996;
sum(1, 1);
V8 解析器是从上往下解析代码的,当解析器遇到函数声明 sum 时,发现它不是立即执行,所以会用 Pre-Parser 解析器对其预解析,过程中只会解析函数声明,不会解析函数内部代码,不会为函数内部代码生成 AST。
之后解释器会把 AST 编译为字节码并执行,解释器会按照自上而下的顺序执行代码,先执行 const a = 666; 和 const c = 996; ,然后执行函数调用 sum(1, 1) ,这时 Parser 解析器才会继续解析函数内的代码、生成 AST,再交给解释器编译执行。
计算机程序语言都运行在对应的代码引擎上,使用内存过程可以分为以下三个步骤:
在 JavaScript 中,当创建变量时,系统会自动给对象分配对应的内存,来看下面的例子:
var a = 123; // 给数值变量分配栈内存
var etf = "ARK"; // 给字符串分配栈内存
// 给对象及其包含的值分配堆内存
var obj = {
name: 'tom',
age: 13
};
// 给数组及其包含的值分配内存
var a = [1, null, "str"];
// 给函数分配内存
function sum(a, b){
return a + b;
}
JavaScript 中的数据分为两类:
栈内存中的基本类型,可以通过操作系统直接处理;而堆内存中的引用类型,正是由于可以经常变化,大小不固定,因此需要 JavaScript 的引擎通过垃圾回收机制来处理。所谓的垃圾回收是指:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间。 Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。
JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。
先来看看 Chrome浏览器的垃圾回收过程:
(1)通过 GC Root 标记空间中活动对象和⾮活动对象。
⽬前 V8 采⽤可访问性算法来判断堆中的对象是否是活动对象。这个算法是将⼀些 GC Root 作为初始存活的对象的集合,从 GC Roots 对象出发,遍历 GC Root 中所有对象:
(2)回收⾮活动对象所占据的内存。
其实就是在所有的标记完成之后,统⼀清理内存中所有被标记为可回收的对象。
(3)内存整理。
⼀般来说,频繁回收对象后,内存中就会存在⼤量不连续空间,这些不连续的内存空间称为内存碎⽚。当内存中出现了⼤量的内存碎⽚之后,如果需要分配较⼤的连续内存时,就有可能出现内存不⾜的情况,所以最后⼀步需要整理这些内存碎⽚。这步其实是可选的,因为有的垃圾回收器不会产⽣内存碎⽚。
以上就是⼤致的垃圾回收流程。⽬前 V8 使用了两个垃圾回收器:主垃圾回收器和副垃圾回收器。下面就来看看 V8 是如何实现垃圾回收的。
在 V8 中,会把堆分为新生代和老生代两个区域,新生代中存放的是生存时间短的对象,老生代中存放生存时间久的对象: 新⽣代通常只⽀持 1~8M 的容量,⽽⽼⽣代⽀持的容量就⼤很多。对于这两块区域,V8分别使⽤两个不同的垃圾回收器,以便更⾼效地实施垃圾回收:
副垃圾回收器主要负责新⽣代的垃圾回收。大多数的对象最开始都会被分配在新生代,该存储空间相对较小,分为两个空间:from 空间(对象区)和 to 空间(空闲区)。
新加⼊的对象都会存放到对象区域,当对象区域快被写满时,就需要执⾏⼀次垃圾清理操作:首先要对对象区域中的垃圾做标记,标记完成之后,就进入垃圾清理阶段。副垃圾回收器会把这些存活的对象复制到空闲区域中,同时它还会把这些对象有序地排列起来。这个复制过程就相当于完成了内存整理操作,复制后空闲区域就没有内存碎片了:
V8-第 7 页.png
完成复制后,对象区域与空闲区域进行角色翻转,也就是原来的对象区域变成空闲区域,原来的空闲区域变成了对象区域,这种算法称之为 Scavenge 算法,这样就完成了垃圾对象的回收操作。同时,这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去:
V8-第 7 页.png
不过,副垃圾回收器每次执⾏清理操作时,都需要将存活的对象从对象区域复制到空闲区域,复制操作需要时间成本,如果新⽣区空间设置得太⼤了,那么每次清理的时间就会过久,所以为了执⾏效率,⼀般新⽣区的空间会被设置得⽐较⼩。 也正是因为新⽣区的空间不⼤,所以很容易被存活的对象装满整个区域,副垃圾回收器⼀旦监控对象装满了,便执⾏垃圾回收。同时,副垃圾回收器还会采⽤对象晋升策略,也就是移动那些经过两次垃圾回收依然还存活的对象到⽼⽣代中。
主垃圾回收器主要负责⽼⽣代中的垃圾回收。除了新⽣代中晋升的对象,⼀些⼤的对象会直接被分配到⽼⽣代⾥。因此,⽼⽣代中的对象有两个特点:
由于⽼⽣代的对象⽐较⼤,若要在⽼⽣代中使⽤ Scavenge 算法进⾏垃圾回收,复制这些⼤的对象将会花费较多时间,从⽽导致回收执⾏效率不⾼,同时还会浪费空间。所以,主垃圾回收器采⽤标记清除的算法进⾏垃圾回收。
这种方式分为标记和清除两个阶段:
这两个阶段如图所示:
V8-第 7 页.png
对垃圾数据进⾏标记,然后清除,这就是标记清除算法,不过对⼀块内存多次执⾏标记清除算法后,会产⽣⼤量不连续的内存碎⽚。⽽碎⽚过多会导致⼤对象⽆法分配到⾜够的连续内存,于是⼜引⼊了另外⼀种算法——标记整理。
这个算法的标记过程仍然与标记清除算法⾥的是⼀样的,先标记可回收对象,但后续步骤不是直接对可回收对象进⾏清理,⽽是让所有存活的对象都向⼀端移动,然后直接清理掉这⼀端之外的内存:
V8-第 8 页.png
我们知道,JavaScript 是单行线语言,运行在主线程上。一旦执行垃圾回收算法,都需要将正在执行的 JavaScript 脚本暂停下来,待垃圾回收完毕后再恢复脚本执行。这种行为叫做全停顿。
主垃圾回收器执行一次完整的垃圾回收流程如下图所示:
在 V8 新生代的垃圾回收中,因其空间较小,且存活对象较少,所以全停顿的影响不大。但老生代中,如果在执行垃圾回收的过程中,占用主线程时间过久,主线程是不能做其他事情的,需要等待执行完垃圾回收操作才能做其他事情,这将就可能会造成页面的卡顿现象。
为了降低老生代的垃圾回收而造成的卡顿,V8 将标记过程分为一个个的子标记过程,同时让垃圾回收标记和 JavaScript 应用逻辑交替进行,直到标记阶段完成,这个算法称为增量标记算法。如下图所示:
使用增量标记算法可以把一个完整的垃圾回收任务拆分为很多小的任务,这些小的任务执行时间比较短,可以穿插在其他的 JavaScript 任务中间执行,这样当执行代码时,就不会让用户因为垃圾回收任务而感受到页面的卡顿了。
虽然浏览器可以进行垃圾自动回收,但是当代码比较复杂时,垃圾回收所带来的代价较大,所以应该尽量减少垃圾回收:
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/lJrk7ufEa5OyKsOvyvSxsg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。