復(fù)習(xí)前端:JavaScript V8 引擎機(jī)制

V8 是谷歌推出的開源 JavaScript 引擎,它是用 C++ 編寫的,支持 Google Chrome、Chromium 網(wǎng)絡(luò)瀏覽器和 NodeJS,它負(fù)責(zé)與環(huán)境交互并生成字節(jié)碼來運(yùn)行程序。
V8 和其他引擎之間最顯著的區(qū)別是它的即時(shí) (JIT) 編譯器。
如何在V8中執(zhí)行一段JS代碼
- 預(yù)分析:檢查語法錯(cuò)誤但不生成 AST
- 生成 AST : 詞法/語法分析后,生成抽象語法樹,AST 為每一行代碼定義鍵值對。初始類型標(biāo)識符定義 AST 屬于一個(gè)程序,然后所有代碼行將定義在主體內(nèi)部,主體是一個(gè)對象數(shù)組。
- 生成字節(jié)碼:基線編譯器(Ignition)將 AST 轉(zhuǎn)換為字節(jié)碼
- 生成機(jī)器代碼:優(yōu)化編譯器 (Turbofan) 將字節(jié)碼轉(zhuǎn)換為優(yōu)化的機(jī)器代碼。另外,在逐行執(zhí)行字節(jié)碼的過程中,如果一段代碼經(jīng)常被執(zhí)行,V8會(huì)直接將這段代碼轉(zhuǎn)換并保存為機(jī)器碼,下次執(zhí)行不需要經(jīng)過字節(jié)碼,優(yōu)化了執(zhí)行速度
引用計(jì)數(shù)和標(biāo)記清除簡介
- 引用計(jì)數(shù):如果一個(gè)變量被分配了一個(gè)引用類型,那么這個(gè)對象的引用次數(shù)是
+1。如果變量變?yōu)榱硪粋€(gè)值,則對象的引用數(shù)為-1,垃圾回收器將回收引用數(shù)為0的對象。但是,當(dāng)對象被循環(huán)引用時(shí),引用數(shù)永遠(yuǎn)不會(huì)歸零,導(dǎo)致無法釋放內(nèi)存。 - 標(biāo)記清除:垃圾收集器首先標(biāo)記內(nèi)存中的所有對象,然后從根節(jié)點(diǎn)開始遍歷,清除被引用對象和運(yùn)行環(huán)境中對象的標(biāo)記,剩下的標(biāo)記對象不可訪問,等待回收對象。
V8如何進(jìn)行垃圾回收
JavaScript 引擎中變量的存儲(chǔ)位置主要有兩個(gè),棧內(nèi)存和堆內(nèi)存。
- 棧內(nèi)存:存放基本類型數(shù)據(jù)和引用類型數(shù)據(jù)的內(nèi)存地址
- 堆內(nèi)存:存放引用類型數(shù)據(jù)。
對于不同類型的變量,棧內(nèi)存和堆內(nèi)存垃圾回收方式不同。
- 棧內(nèi)存的回收:調(diào)用棧上下文切換后回收棧內(nèi)存,比較簡單
- 堆內(nèi)存回收:V8的堆內(nèi)存分為新生代內(nèi)存和老年代內(nèi)存。新生代內(nèi)存是臨時(shí)分配的內(nèi)存,存在時(shí)間短,老年代內(nèi)存存在時(shí)間長。
新一代內(nèi)存回收機(jī)制
新一代內(nèi)存容量較小,64位系統(tǒng)下只有32M,新生代的記憶分為兩部分:From 和 To 。進(jìn)行垃圾回收時(shí),先掃描 From,回收非存活對象,存活對象按順序復(fù)制到 To,然后交換From/To,等待下一次回收
老年代內(nèi)存回收機(jī)制
- Promotion:如果新生代的變量經(jīng)過多次回收后仍然存在,則將其放入老年代的內(nèi)存中
- 標(biāo)記清除:老年代內(nèi)存會(huì)先遍歷所有對象并標(biāo)記,然后對正在使用或強(qiáng)引用的對象取消標(biāo)記,回收標(biāo)記的對象
- 內(nèi)存碎片整理:將對象移動(dòng)到內(nèi)存的一端
為什么JS比C++等語言慢,V8做了哪些優(yōu)化?
JS 的問題
- 動(dòng)態(tài)類型:每次訪問屬性/查找方法時(shí),都需要先檢查類型;另外,動(dòng)態(tài)類型在編譯階段很難優(yōu)化
- 屬性訪問:在
C++/Java等語言中,方法、屬性都是保存在數(shù)組中,只能通過數(shù)組位移獲取,而JS是保存在對象中,每次獲取都要進(jìn)行hash查詢。
針對 V8 的優(yōu)化
優(yōu)化的JIT(即時(shí)編譯):與 C++/Java 等編譯型語言相比,JS是同時(shí)解釋和執(zhí)行的,效率低下。V8 優(yōu)化了這個(gè)過程:如果一段代碼被執(zhí)行多次,V8 會(huì)將這段代碼轉(zhuǎn)換成機(jī)器碼并緩存起來,下次運(yùn)行時(shí)直接使用機(jī)器碼。
隱藏類:對于 C++ 等語言,只需幾條指令就可以通過偏移獲取變量信息,而JS需要進(jìn)行字符串匹配,效率低下。V8借用類和偏移位置的思想,將對象劃分為不同的組,即隱藏類。
嵌入式緩存:即緩存對象查詢的結(jié)果。一般的查詢過程是:獲取隱藏類地址->根據(jù)屬性名找到偏移值->計(jì)算屬性地址,嵌入式緩存就是這個(gè)過程結(jié)果的緩存。
總結(jié)
每當(dāng)討論 JavaScript 的工作原理時(shí),都會(huì)談?wù)撌录h(huán)、微任務(wù)、宏任務(wù)和回調(diào)隊(duì)列。然而,所有這些東西都沒有在 JavaScript 中實(shí)現(xiàn)。相反,它們是 V8 引擎的一部分,負(fù)責(zé)優(yōu)化 JavaScript 代碼。