<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          萬(wàn)字干貨!詳解JavaScript執(zhí)行過(guò)程

          共 11147字,需瀏覽 23分鐘

           ·

          2022-02-12 14:19

          js代碼的執(zhí)行,主要分為兩個(gè)個(gè)階段:編譯階段、執(zhí)行階段!本文所有內(nèi)容基于V8引擎。

          1前言

          v8引擎

          v8引擎工作原理:

          V8由許多子模塊構(gòu)成,其中這4個(gè)模塊是最重要的:

          • Parser:負(fù)責(zé)將JavaScript源碼轉(zhuǎn)換為Abstract Syntax Tree (AST);
            • 如果函數(shù)沒(méi)有被調(diào)用,那么是不會(huì)被轉(zhuǎn)換成AST的
          • Ignition:interpreter,即解釋器,負(fù)責(zé)將AST轉(zhuǎn)換為Bytecode,解釋執(zhí)行Bytecode;同時(shí)收集TurboFan優(yōu)化編譯所需的信息,比如函數(shù)參數(shù)的類(lèi)型,有了類(lèi)型才能進(jìn)行真實(shí)的運(yùn)算;
            • 如果函數(shù)只調(diào)用一次,Ignition會(huì)執(zhí)行解釋執(zhí)行ByteCode
            • 解釋器也有解釋執(zhí)行bytecode的能力

          通常有兩種類(lèi)型的解釋器,基于棧 (Stack-based)基于寄存器 (Register-based),基于棧的解釋器使用棧來(lái)保存函數(shù)參數(shù)、中間運(yùn)算結(jié)果、變量等;基于寄存器的虛擬機(jī)則支持寄存器的指令操作,使用寄存器來(lái)保存參數(shù)、中間計(jì)算結(jié)果。通常,基于棧的虛擬機(jī)也定義了少量的寄存器,基于寄存器的虛擬機(jī)也有堆棧,其區(qū)別體現(xiàn)在它們提供的指令集體系。大多數(shù)解釋器都是基于棧的,比如 Java 虛擬機(jī).Net 虛擬機(jī),還有早期的 V8 虛擬機(jī)。基于堆棧的虛擬機(jī)在處理函數(shù)調(diào)用、解決遞歸問(wèn)題和切換上下文時(shí)簡(jiǎn)單明快。而現(xiàn)在的 V8 虛擬機(jī)則采用了基于寄存器的設(shè)計(jì),它將一些中間數(shù)據(jù)保存到寄存器中。基于寄存器的解釋器架構(gòu):

          • TurboFan:compiler,即編譯器,利用Ignitio所收集的類(lèi)型信息,將Bytecode轉(zhuǎn)換為優(yōu)化的匯編代碼;
            • 如果一個(gè)函數(shù)被多次調(diào)用,那么就會(huì)被標(biāo)記為熱點(diǎn)函數(shù),那么就會(huì)經(jīng)過(guò)TurboFan轉(zhuǎn)換成優(yōu)化的機(jī)器碼,提高代碼的執(zhí)行性能;

            • 但是,機(jī)器碼實(shí)際上也會(huì)被還原為ByteCode,這是因?yàn)槿绻罄m(xù)執(zhí)行函數(shù)的過(guò)程中,類(lèi)型發(fā)生了變化(比如sum函數(shù)原來(lái)執(zhí)行的是number類(lèi)型,后來(lái)執(zhí)行變成了string類(lèi)型),之前優(yōu)化的機(jī)器碼并不能正確的處理運(yùn)算,就會(huì)逆向的轉(zhuǎn)換成字節(jié)碼;

          • Orinoco:garbage collector,垃圾回收模塊,負(fù)責(zé)將程序不再需要的內(nèi)存空間回收;

          提一嘴

          棧 stack

          棧的特點(diǎn)是"LIFO,即后進(jìn)先出(Last in, first out)"。數(shù)據(jù)存儲(chǔ)時(shí)只能從頂部逐個(gè)存入,取出時(shí)也需從頂部逐個(gè)取出。

          堆 heap

          堆的特點(diǎn)是"無(wú)序"的key-value"鍵值對(duì)"存儲(chǔ)方式。堆的存取方式跟順序沒(méi)有關(guān)系,不局限出入口。

          隊(duì)列 queue

          隊(duì)列的特點(diǎn)是是"FIFO,即先進(jìn)先出(First in, first out)" 。數(shù)據(jù)存取時(shí)"從隊(duì)尾插入,從隊(duì)頭取出"。

          "與棧的區(qū)別:棧的存入取出都在頂部一個(gè)出入口,而隊(duì)列分兩個(gè),一個(gè)出口,一個(gè)入口"。

          2編譯階段

          詞法分析 Scanner

          將由字符組成的字符串分解成(對(duì)編程語(yǔ)言來(lái)說(shuō))有意義的代碼塊,這些代碼塊被稱(chēng)為詞法單元(token)。

          [
          ????{
          ????????"type":?"Keyword",
          ????????"value":?"var"
          ????},
          ????{
          ????????"type":?"Identifier",
          ????????"value":?"name"
          ????},
          ????{
          ????????"type":?"Punctuator",
          ????????"value":?"="
          ????},
          ????{
          ????????"type":?"String",
          ????????"value":?"'finget'"
          ????},
          ????{
          ????????"type":?"Punctuator",
          ????????"value":?";"
          ????}
          ]

          語(yǔ)法分析 Parser

          這個(gè)過(guò)程是將詞法單元流(數(shù)組)轉(zhuǎn)換成一個(gè)由元素逐級(jí)嵌套所組成的代表了程序語(yǔ)法結(jié)構(gòu)的樹(shù)。這個(gè)樹(shù)被稱(chēng)為“抽象語(yǔ)法樹(shù)”(Abstract Syntax Tree,AST)。

          {
          "type": "Program",
          "body": [
          {
          "type": "VariableDeclaration",
          "declarations": [
          {
          "type": "VariableDeclarator",
          "id": {
          "type": "Identifier",
          "name": "name"
          },
          "init": {
          "type": "Literal",
          "value": "finget",
          "raw": "'finget'"
          }
          }
          ],
          "kind": "var"
          }
          ],
          "sourceType": "script"
          }

          在此過(guò)程中,如果源代碼不符合語(yǔ)法規(guī)則,則會(huì)終止,并拋出“語(yǔ)法錯(cuò)誤”。

          這里有個(gè)工具,可以實(shí)時(shí)生成語(yǔ)法樹(shù),可以試試esprima。

          字節(jié)碼生成

          可以用node node --print-bytecode查看字節(jié)碼:

          //?test.js
          function?getMyname()?{
          ?var?myname?=?'finget';
          ?console.log(myname);
          }
          getMyname();
          node --print-bytecode test.js 

          ...
          [generated bytecode for function: getMyname (0x10ca700104e9 )]
          Parameter count 1
          Register count 3
          Frame size 24
          18 E> 0x10ca70010e86 @ 0 : a7 StackCheck
          37 S> 0x10ca70010e87 @ 1 : 12 00 LdaConstant [0]
          0x10ca70010e89 @ 3 : 26 fb Star r0
          48 S> 0x10ca70010e8b @ 5 : 13 01 00 LdaGlobal [1], [0]
          0x10ca70010e8e @ 8 : 26 f9 Star r2
          56 E> 0x10ca70010e90 @ 10 : 28 f9 02 02 LdaNamedProperty r2, [2], [2]
          0x10ca70010e94 @ 14 : 26 fa Star r1
          56 E> 0x10ca70010e96 @ 16 : 59 fa f9 fb 04 CallProperty1 r1, r2, r0, [4]
          0x10ca70010e9b @ 21 : 0d LdaUndefined
          69 S> 0x10ca70010e9c @ 22 : ab Return
          Constant pool (size = 3)
          Handler Table (size = 0)
          ...

          這里涉及到一個(gè)很重要的概念:JIT(Just-in-time)一邊解釋?zhuān)贿厛?zhí)行。

          它是如何工作的呢(結(jié)合第一張流程圖來(lái)看):

          1.在 JavaScript 引擎中增加一個(gè)監(jiān)視器(也叫分析器)。監(jiān)視器監(jiān)控著代碼的運(yùn)行情況,記錄代碼一共運(yùn)行了多少次、如何運(yùn)行的等信息,如果同一行代碼運(yùn)行了幾次,這個(gè)代碼段就被標(biāo)記成了 “warm”,如果運(yùn)行了很多次,則被標(biāo)記成 “hot”;

          2.(基線(xiàn)編譯器)如果一段代碼變成了 “warm”,那么 JIT 就把它送到基線(xiàn)編譯器去編譯,并且把編譯結(jié)果存儲(chǔ)起來(lái)。比如,監(jiān)視器監(jiān)視到了,某行、某個(gè)變量執(zhí)行同樣的代碼、使用了同樣的變量類(lèi)型,那么就會(huì)把編譯后的版本,替換這一行代碼的執(zhí)行,并且存儲(chǔ);

          3.(優(yōu)化編譯器)如果一個(gè)代碼段變得 “hot”,監(jiān)視器會(huì)把它發(fā)送到優(yōu)化編譯器中。生成一個(gè)更快速和高效的代碼版本出來(lái),并且存儲(chǔ)。例如:循環(huán)加一個(gè)對(duì)象屬性時(shí),假設(shè)它是 INT 類(lèi)型,優(yōu)先做 INT 類(lèi)型的判斷;

          4.(反優(yōu)化 Deoptimization)可是對(duì)于 JavaScript 從來(lái)就沒(méi)有確定這么一說(shuō),前 99 個(gè)對(duì)象屬性保持著 INT 類(lèi)型,可能第 100 個(gè)就沒(méi)有這個(gè)屬性了,那么這時(shí)候 JIT 會(huì)認(rèn)為做了一個(gè)錯(cuò)誤的假設(shè),并且把優(yōu)化代碼丟掉,執(zhí)行過(guò)程將會(huì)回到解釋器或者基線(xiàn)編譯器,這一過(guò)程叫做反優(yōu)化。

          作用域

          作用域是一套規(guī)則,用來(lái)管理引擎如何查找變量。在es5之前,js只有全局作用域函數(shù)作用域。es6引入了塊級(jí)作用域。但是這個(gè)塊級(jí)別作用域需要注意的是不是{}的作用域,而是letconst關(guān)鍵字的塊級(jí)作用域

          var?name?=?'FinGet';

          function?fn()?{
          ??var?age?=?18;
          ??console.log(name);
          }

          在解析時(shí)就會(huì)確定作用域:

          簡(jiǎn)單的來(lái)說(shuō),作用域就是個(gè)盒子,規(guī)定了變量和函數(shù)的可訪(fǎng)問(wèn)范圍以及他們的生命周期。

          詞法作用域

          詞法作用域就是指作用域是由代碼中函數(shù)聲明的位置來(lái)決定的,所以詞法作用域是靜態(tài)的作用域,通過(guò)它就能夠預(yù)測(cè)代碼在執(zhí)行過(guò)程中如何查找標(biāo)識(shí)符。

          function?fn()?{
          ????console.log(myName)
          }
          function?fn1()?{
          ????var?myName?=?"?FinGet?"
          ????fn()
          }
          var?myName?=?"?global_finget?"
          fn1()

          上面代碼打印的結(jié)果是:global_finget,這就是因?yàn)樵诰幾g階段就已經(jīng)確定了作用域,fn是定義在全局作用域中的,它在自己內(nèi)部找不到myName就會(huì)去全局作用域中找,不會(huì)在fn1中查找。

          3執(zhí)行階段

          執(zhí)行上下文

          遇到函數(shù)執(zhí)行的時(shí)候,就會(huì)創(chuàng)建一個(gè)執(zhí)行上下文。執(zhí)行上下文是當(dāng)前 JavaScript 代碼被解析和執(zhí)行時(shí)所在環(huán)境的抽象概念。

          JavaScript 中有三種執(zhí)行上下文類(lèi)型:

          • 全局執(zhí)行上下文 (只有一個(gè))
          • 函數(shù)執(zhí)行上下文
          • eval

          執(zhí)行上下文的創(chuàng)建分為兩個(gè)階段創(chuàng)建:1.創(chuàng)建階段 2.執(zhí)行階段

          創(chuàng)建階段

          在任意的 JavaScript 代碼被執(zhí)行時(shí),執(zhí)行上下文處于創(chuàng)建階段。在創(chuàng)建階段中總共發(fā)生了三件事情:

          • 確定 this 的值,也被稱(chēng)為 This Binding
          • LexicalEnvironment(詞法環(huán)境) 組件被創(chuàng)建。
          • VariableEnvironment(變量環(huán)境) 組件被創(chuàng)建。
          ExecutionContext?=?{??
          ??ThisBinding?=?<this?value>,?????//?確定this?
          ??LexicalEnvironment?=?{?...?},???//?詞法環(huán)境
          ??VariableEnvironment?=?{?...?},??//?變量環(huán)境
          }
          This Binding

          在全局執(zhí)行上下文中,this 的值指向全局對(duì)象,在瀏覽器中,this 的值指向 window 對(duì)象。在函數(shù)執(zhí)行上下文中,this 的值取決于函數(shù)的調(diào)用方式。如果它被一個(gè)對(duì)象引用調(diào)用,那么 this 的值被設(shè)置為該對(duì)象,否則 this 的值被設(shè)置為全局對(duì)象或 undefined(嚴(yán)格模式下)。

          詞法環(huán)境(Lexical Environment)

          詞法環(huán)境是一個(gè)包含標(biāo)識(shí)符變量映射的結(jié)構(gòu)。(這里的標(biāo)識(shí)符表示變量/函數(shù)的名稱(chēng),變量是對(duì)實(shí)際對(duì)象【包括函數(shù)類(lèi)型對(duì)象】或原始值的引用)。在詞法環(huán)境中,有兩個(gè)組成部分:(1)環(huán)境記錄(environment record) (2)對(duì)外部環(huán)境的引用

          • 環(huán)境記錄是存儲(chǔ)變量函數(shù)聲明的實(shí)際位置。
          • 對(duì)外部環(huán)境的引用意味著它可以訪(fǎng)問(wèn)其外部詞法環(huán)境。(實(shí)現(xiàn)作用域鏈的重要部分)

          詞法環(huán)境有兩種類(lèi)型:

          • 全局環(huán)境(在全局執(zhí)行上下文中)是一個(gè)沒(méi)有外部環(huán)境的詞法環(huán)境。全局環(huán)境的外部環(huán)境引用為 null。它擁有一個(gè)全局對(duì)象(window 對(duì)象)及其關(guān)聯(lián)的方法和屬性(例如數(shù)組方法)以及任何用戶(hù)自定義的全局變量,this 的值指向這個(gè)全局對(duì)象。

          • 函數(shù)環(huán)境,用戶(hù)在函數(shù)中定義的變量被存儲(chǔ)在環(huán)境記錄中。對(duì)外部環(huán)境的引用可以是全局環(huán)境,也可以是包含內(nèi)部函數(shù)的外部函數(shù)環(huán)境。

          注意:對(duì)于函數(shù)環(huán)境而言,環(huán)境記錄 還包含了一個(gè) arguments 對(duì)象,該對(duì)象包含了索引和傳遞給函數(shù)的參數(shù)之間的映射以及傳遞給函數(shù)的參數(shù)的長(zhǎng)度(數(shù)量)。

          變量環(huán)境 Variable Environment

          它也是一個(gè)詞法環(huán)境,其 EnvironmentRecord 包含了由 VariableStatements 在此執(zhí)行上下文創(chuàng)建的綁定。

          如上所述,變量環(huán)境也是一個(gè)詞法環(huán)境,因此它具有上面定義的詞法環(huán)境的所有屬性。

          示例代碼:

          let?a?=?20;??
          const?b?=?30;??
          var?c;

          function?multiply(e,?f)?{??
          ?var?g?=?20;??
          ?return?e?*?f?*?g;??
          }

          c?=?multiply(20,?30);

          執(zhí)行上下文:

          GlobalExectionContext?=?{

          ??ThisBinding:?,

          ??LexicalEnvironment:?{??
          ????EnvironmentRecord:?{??
          ??????Type:?"Object",??
          ??????//?標(biāo)識(shí)符綁定在這里??
          ??????a:?,??
          ??????b:?,??
          ??????multiply:???
          ????}??
          ????outer:???
          ??},

          ??VariableEnvironment:?{??
          ????EnvironmentRecord:?{??
          ??????Type:?"Object",??
          ??????//?標(biāo)識(shí)符綁定在這里??
          ??????c:?undefined,??
          ????}??
          ????outer:???
          ??}??
          }

          FunctionExectionContext?=?{??
          ???
          ??ThisBinding:?,

          ??LexicalEnvironment:?{??
          ????EnvironmentRecord:?{??
          ??????Type:?"Declarative",??
          ??????//?標(biāo)識(shí)符綁定在這里??
          ??????Arguments:?{0:?20,?1:?30,?length:?2},??
          ????},??
          ????outer:???//?指定全局環(huán)境
          ??},

          ??VariableEnvironment:?{??
          ????EnvironmentRecord:?{??
          ??????Type:?"Declarative",??
          ??????//?標(biāo)識(shí)符綁定在這里??
          ??????g:?undefined??
          ????},??
          ????outer:???
          ??}??
          }

          仔細(xì)看上面的:a: < uninitialized >,c: undefined。所以你在let a定義前console.log(a)的時(shí)候會(huì)得到Uncaught ReferenceError: Cannot access 'a' before initialization

          為什么要有兩個(gè)詞法環(huán)境

          變量環(huán)境組件(VariableEnvironment) 是用來(lái)登記var function變量聲明,詞法環(huán)境組件(LexicalEnvironment)是用來(lái)登記let const class等變量聲明。

          在ES6之前都沒(méi)有塊級(jí)作用域,ES6之后我們可以用let const來(lái)聲明塊級(jí)作用域,有這兩個(gè)詞法環(huán)境是為了實(shí)現(xiàn)塊級(jí)作用域的同時(shí)不影響var變量聲明和函數(shù)聲明,具體如下:

          1. 首先在一個(gè)正在運(yùn)行的執(zhí)行上下文內(nèi),詞法環(huán)境由LexicalEnvironmentVariableEnvironment構(gòu)成,用來(lái)登記所有的變量聲明。
          2. 當(dāng)執(zhí)行到塊級(jí)代碼時(shí)候,會(huì)先LexicalEnvironment記錄下來(lái),記錄為oldEnv
          3. 創(chuàng)建一個(gè)新的LexicalEnvironment(outer指向oldEnv),記錄為newEnv,并將newEnv設(shè)置為正在執(zhí)行上下文的LexicalEnvironment
          4. 塊級(jí)代碼內(nèi)的let const會(huì)登記在newEnv里面,但是var聲明和函數(shù)聲明還是登記在原來(lái)的VariableEnvironment里。
          5. 塊級(jí)代碼執(zhí)行結(jié)束后,將oldEnv還原為正在執(zhí)行上下文的LexicalEnvironment
          function?foo(){
          ????var?a?=?1
          ????let?b?=?2
          ????{
          ??????let?b?=?3
          ??????var?c?=?4
          ??????let?d?=?5
          ??????console.log(a)
          ??????console.log(b)
          ????}
          ????console.log(b)?
          ????console.log(c)
          ????console.log(d)
          }???
          foo()

          從圖中可以看出,當(dāng)進(jìn)入函數(shù)的作用域塊時(shí),作用域塊中通過(guò)let聲明的變量,會(huì)被存放在詞法環(huán)境的一個(gè)單獨(dú)的區(qū)域中,這個(gè)區(qū)域中的變量并不影響作用域塊外面的變量,比如在作用域外面聲明了變量b,在該作用域塊內(nèi)部也聲明了變量b,當(dāng)執(zhí)行到作用域內(nèi)部時(shí),它們都是獨(dú)立的存在。

          其實(shí),在詞法環(huán)境內(nèi)部,維護(hù)了一個(gè)小型棧結(jié)構(gòu),棧底是函數(shù)最外層的變量,進(jìn)入一個(gè)作用域塊后,就會(huì)把該作用域塊內(nèi)部的變量壓到棧頂;當(dāng)作用域執(zhí)行完成之后,該作用域的信息就會(huì)從棧頂彈出,這就是詞法環(huán)境的結(jié)構(gòu)。需要注意下,我這里所講的變量是指通過(guò)let或者const聲明的變量。

          再接下來(lái),當(dāng)執(zhí)行到作用域塊中的console.log(a)這行代碼時(shí),就需要在詞法環(huán)境和變量環(huán)境中查找變量a的值了,具體查找方式是:沿著詞法環(huán)境的棧頂向下查詢(xún),如果在詞法環(huán)境中的某個(gè)塊中查找到了,就直接返回給JavaScript引擎,如果沒(méi)有查找到,那么繼續(xù)在變量環(huán)境中查找。

          執(zhí)行棧 Execution Context Stack

          每個(gè)函數(shù)都會(huì)有自己的執(zhí)行上下文,多個(gè)執(zhí)行上下文就會(huì)以棧(調(diào)用棧)的方式來(lái)管理。

          function?a?()?{
          ??console.log('In?fn?a')
          ??function?b?()?{
          ????console.log('In?fn?b')
          ????function?c?()?{
          ??????console.log('In?fn?c')
          ????}
          ????c()
          ??}
          ??b()
          }
          a()

          可以用這個(gè)工具試一下,更直觀(guān)的觀(guān)察進(jìn)棧和出棧javascript visualizer 工具。

          看這個(gè)圖就可以看出作用域鏈了吧,很直觀(guān)。作用域鏈就是在執(zhí)行上下文創(chuàng)建階段確定的。有了執(zhí)行的環(huán)境,才能確定它應(yīng)該和誰(shuí)構(gòu)成作用域鏈。

          4V8垃圾回收

          內(nèi)存分配

          棧是臨時(shí)存儲(chǔ)空間,主要存儲(chǔ)局部變量和函數(shù)調(diào)用,內(nèi)小且存儲(chǔ)連續(xù),操作起來(lái)簡(jiǎn)單方便,一般由系統(tǒng)自動(dòng)分配自動(dòng)回收,所以文章內(nèi)所說(shuō)的垃圾回收,都是基于堆內(nèi)存。

          基本類(lèi)型數(shù)據(jù)(Number, Boolean, String, Null, Undefined, Symbol, BigInt)保存在在棧內(nèi)存中。引用類(lèi)型數(shù)據(jù)保存在堆內(nèi)存中,引用數(shù)據(jù)類(lèi)型的變量是一個(gè)指向堆內(nèi)存中實(shí)際對(duì)象的引用,存在棧中。

          為什么基本數(shù)據(jù)類(lèi)型存儲(chǔ)在棧中,引用數(shù)據(jù)類(lèi)型存儲(chǔ)在堆中?

          JavaScript引擎需要用棧來(lái)維護(hù)程序執(zhí)行期間的上下文的狀態(tài),如果棧空間大了的話(huà),所有數(shù)據(jù)都存放在棧空間里面,會(huì)影響到上下文切換的效率,進(jìn)而影響整個(gè)程序的執(zhí)行效率。

          這里用來(lái)存儲(chǔ)對(duì)象和動(dòng)態(tài)數(shù)據(jù),這是內(nèi)存中最大的區(qū)域,并且是GC(Garbage collection 垃圾回收)工作的地方。不過(guò),并不是所有的堆內(nèi)存都可以進(jìn)行GC,只有新生代和老生代被GC管理。堆可以進(jìn)一步細(xì)分為下面這樣:

          • 新生代空間:是最新產(chǎn)生的數(shù)據(jù)存活的地方,這些數(shù)據(jù)往往都是短暫的。這個(gè)空間被一分為二,然后被Scavenger(Minor GC)所管理。稍后會(huì)介紹。可以通過(guò)V8標(biāo)志如 --max_semi_space_size 或 --min_semi_space_size 來(lái)控制新生代空間大小
          • 老生代空間:是從新生代空間經(jīng)過(guò)至少兩輪Minor GC仍然存活下來(lái)的數(shù)據(jù),該空間被Major GC(Mark-Sweep & Mark-Compact)管理,稍后會(huì)介紹。可以通過(guò) --initial_old_space_size 或 --max_old_space_size控制空間大小。

          Old pointer space:存活下來(lái)的包含指向其他對(duì)象指針的對(duì)象

          Old data space:存活下來(lái)的只包含數(shù)據(jù)的對(duì)象。

          • 大對(duì)象空間:這是比空間大小還要大的對(duì)象,大對(duì)象不會(huì)被gc處理。
          • 代碼空間:這里是JIT所編譯的代碼。這是除了在大對(duì)象空間中分配代碼并執(zhí)行之外的唯一可執(zhí)行的空間。
          • map空間:存放 Cell 和 Map,每個(gè)區(qū)域都是存放相同大小的元素,結(jié)構(gòu)簡(jiǎn)單。

          代際假說(shuō)

          代際假說(shuō)有以下兩個(gè)特點(diǎn):

          • 第一個(gè)是大部分對(duì)象在內(nèi)存中存在的時(shí)間很短,簡(jiǎn)單來(lái)說(shuō),就是很多對(duì)象一經(jīng)分配內(nèi)存,很快就變得不可訪(fǎng)問(wèn);
          • 第二個(gè)是不死的對(duì)象,會(huì)活得更久。

          在 V8 中會(huì)把堆分為新生代老生代兩個(gè)區(qū)域,新生代中存放的是生存時(shí)間短的對(duì)象,老生代中存放的生存時(shí)間久的對(duì)象。

          新生區(qū)通常只支持 1~8M 的容量,而老生區(qū)支持的容量就大很多了。對(duì)于這兩塊區(qū)域,V8 分別使用兩個(gè)不同的垃圾回收器,以便更高效地實(shí)施垃圾回收。

          • 副垃圾回收器,主要負(fù)責(zé)新生代的垃圾回收。
          • 主垃圾回收器,主要負(fù)責(zé)老生代的垃圾回收。

          新生代中用Scavenge算法來(lái)處理。所謂 Scavenge 算法,是把新生代空間對(duì)半劃分為兩個(gè)區(qū)域,一半是對(duì)象區(qū)域,一半是空閑區(qū)域。

          新生代回收

          新加入的對(duì)象都會(huì)存放到對(duì)象區(qū)域,當(dāng)對(duì)象區(qū)域快被寫(xiě)滿(mǎn)時(shí),就需要執(zhí)行一次垃圾清理操作。

          1. 先標(biāo)記需要回收的對(duì)象,然后把對(duì)象區(qū)激活對(duì)象復(fù)制到空閑區(qū),并排序;
          2. 完成復(fù)制后,對(duì)象區(qū)域與空閑區(qū)域進(jìn)行角色翻轉(zhuǎn),也就是原來(lái)的對(duì)象區(qū)域變成空閑區(qū)域,原來(lái)的空閑區(qū)域變成了對(duì)象區(qū)域。

          由于新生代中采用的 Scavenge 算法,所以每次執(zhí)行清理操作時(shí),都需要將存活的對(duì)象從對(duì)象區(qū)域復(fù)制到空閑區(qū)域。但復(fù)制操作需要時(shí)間成本,如果新生區(qū)空間設(shè)置得太大了,那么每次清理的時(shí)間就會(huì)過(guò)久,所以為了執(zhí)行效率,一般新生區(qū)的空間會(huì)被設(shè)置得比較小。

          也正是因?yàn)樾律鷧^(qū)的空間不大,所以很容易被存活的對(duì)象裝滿(mǎn)整個(gè)區(qū)域。為了解決這個(gè)問(wèn)題,JavaScript 引擎采用了對(duì)象晉升策略,也就是經(jīng)過(guò)兩次垃圾回收依然還存活的對(duì)象,會(huì)被移動(dòng)到老生區(qū)中。

          老生代回收

          Mark-Sweep

          Mark-Sweep處理時(shí)分為兩階段,標(biāo)記階段和清理階段,看起來(lái)與Scavenge類(lèi)似,不同的是,Scavenge算法是復(fù)制活動(dòng)對(duì)象,而由于在老生代中活動(dòng)對(duì)象占大多數(shù),所以Mark-Sweep在標(biāo)記了活動(dòng)對(duì)象和非活動(dòng)對(duì)象之后,直接把非活動(dòng)對(duì)象清除。

          • 標(biāo)記階段:對(duì)老生代進(jìn)行第一次掃描,標(biāo)記活動(dòng)對(duì)象
          • 清理階段:對(duì)老生代進(jìn)行第二次掃描,清除未被標(biāo)記的對(duì)象,即清理非活動(dòng)對(duì)象

          Mark-Compact

          由于Mark-Sweep完成之后,老生代的內(nèi)存中產(chǎn)生了很多內(nèi)存碎片,若不清理這些內(nèi)存碎片,如果出現(xiàn)需要分配一個(gè)大對(duì)象的時(shí)候,這時(shí)所有的碎片空間都完全無(wú)法完成分配,就會(huì)提前觸發(fā)垃圾回收,而這次回收其實(shí)不是必要的。

          為了解決內(nèi)存碎片問(wèn)題,Mark-Compact被提出,它是在是在 Mark-Sweep的基礎(chǔ)上演進(jìn)而來(lái)的,相比Mark-Sweep,Mark-Compact添加了活動(dòng)對(duì)象整理階段,將所有的活動(dòng)對(duì)象往一端移動(dòng),移動(dòng)完成后,直接清理掉邊界外的內(nèi)存。

          全停頓 Stop-The-World

          垃圾回收如果耗費(fèi)時(shí)間,那么主線(xiàn)程的JS操作就要停下來(lái)等待垃圾回收完成繼續(xù)執(zhí)行,我們把這種行為叫做全停頓(Stop-The-World)。

          增量標(biāo)記

          為了降低老生代的垃圾回收而造成的卡頓,V8 將標(biāo)記過(guò)程分為一個(gè)個(gè)的子標(biāo)記過(guò)程,同時(shí)讓垃圾回收標(biāo)記和 JavaScript 應(yīng)用邏輯交替進(jìn)行,直到標(biāo)記階段完成,我們把這個(gè)算法稱(chēng)為增量標(biāo)記(Incremental Marking)算法。如下圖所示:

          惰性清理

          增量標(biāo)記只是對(duì)活動(dòng)對(duì)象和非活動(dòng)對(duì)象進(jìn)行標(biāo)記,惰性清理用來(lái)真正的清理釋放內(nèi)存。當(dāng)增量標(biāo)記完成后,假如當(dāng)前的可用內(nèi)存足以讓我們快速的執(zhí)行代碼,其實(shí)我們是沒(méi)必要立即清理內(nèi)存的,可以將清理的過(guò)程延遲一下,讓JavaScript邏輯代碼先執(zhí)行,也無(wú)需一次性清理完所有非活動(dòng)對(duì)象內(nèi)存,垃圾回收器會(huì)按需逐一進(jìn)行清理,直到所有的頁(yè)都清理完畢。

          并發(fā)回收

          并發(fā)式GC允許在在垃圾回收的同時(shí)不需要將主線(xiàn)程掛起,兩者可以同時(shí)進(jìn)行,只有在個(gè)別時(shí)候需要短暫停下來(lái)讓垃圾回收器做一些特殊的操作。但是這種方式也要面對(duì)增量回收的問(wèn)題,就是在垃圾回收過(guò)程中,由于JavaScript代碼在執(zhí)行,堆中的對(duì)象的引用關(guān)系隨時(shí)可能會(huì)變化,所以也要進(jìn)行寫(xiě)屏障操作。

          并行回收

          并行式GC允許主線(xiàn)程和輔助線(xiàn)程同時(shí)執(zhí)行同樣的GC工作,這樣可以讓輔助線(xiàn)程來(lái)分擔(dān)主線(xiàn)程的GC工作,使得垃圾回收所耗費(fèi)的時(shí)間等于總時(shí)間除以參與的線(xiàn)程數(shù)量(加上一些同步開(kāi)銷(xiāo))。

          5站在巨人的肩膀上

          在這里對(duì)前輩大佬表示敬意,查找了很多資料,如有遺漏,還請(qǐng)見(jiàn)諒。文中如果有誤,還望及時(shí)指出,感謝!

          • 瀏覽器工作原理與實(shí)踐
          • 讀李老課程引發(fā)的思考之JS執(zhí)行機(jī)制-|超級(jí) · 奧義|
          • 瀏覽器原理學(xué)習(xí)筆記-瀏覽器中 js 執(zhí)行機(jī)制(上)
          • js引擎的執(zhí)行過(guò)程
          • 初步理解 JavaScript 底層原理
          • JavaScript語(yǔ)言在引擎級(jí)別的執(zhí)行過(guò)程
          • 前端基礎(chǔ) | js執(zhí)行過(guò)程你了解多少?
          • 【編譯】代碼是如何運(yùn)行的之JavaScript執(zhí)行過(guò)程
          • V8是如何執(zhí)行JavaScript代碼的?
          • 視野前端(二)V8引擎是如何工作的
          • 深入了解JavaScript執(zhí)行過(guò)程(JS系列之一)
          • 深入淺出講解V8引擎如何執(zhí)行JavaScript代碼
          • 瀏覽器是如何工作的:Chrome V8讓你更懂JavaScript
          • 如何理解js的執(zhí)行上下文與執(zhí)行棧
          • js執(zhí)行可視化
          • 【譯】理解 Javascript 執(zhí)行上下文和執(zhí)行棧
          • 【譯】理解 Javascript 執(zhí)行上下文和執(zhí)行棧
          • JS作用域鏈的詳解
          • 瀏覽器的垃圾回收詳解(以谷歌瀏覽器的V8為例)
          • 深入理解谷歌最強(qiáng)V8垃圾回收機(jī)制
          • 「譯」Orinoco: V8的垃圾回收器
          • V8內(nèi)存管理及垃圾回收機(jī)制
          • JS Memory Leak And V8 Garbage Collection


          往期干貨

          ?26個(gè)經(jīng)典微信小程序+35套微信小程序源碼+微信小程序合集源碼下載(免費(fèi))

          ?干貨~~~2021最新前端學(xué)習(xí)視頻~~速度領(lǐng)取

          ?前端書(shū)籍-前端290本高清pdf電子書(shū)打包下載


          點(diǎn)贊和在看就是最大的支持??


          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉操逼网 | 国产精品久久久一区二区三区四区 | 五月婷婷久久丁香桃色网 | 操操电影网 | 屄屄屄屄屄屄屄屄屄屄屄屄视频在线免费看 |