V8工作原理

JavaScript 共有8中數據類型,其中有7中原始類型

(Number\String\Boolean\undefined\null\BigInt\Symbol)和1種引用類型(Object)。原始類型的賦值會完整的賦值變量值,引用類型的賦值是複製引用地址。

JavaScript 的內存模型分為三種:代碼空間、棧空間、堆空間。

棧空間和堆空間

原始類型的數據存放在中,引用類型存在堆中。堆中的數據是通過引用和變量關聯起來的,JavaScript 的變量沒有數據類型,值才有數據類型,變量可以持有任何數據類型的數據。

V8工作原理

堆空間存儲

為什麼引用數據類型放在空間?因為該類型數據佔用的空間往往比較大,如果放在中,會影響到調用棧的執行上下文的切換效率,也可能導致棧空間不足;而堆空間比較大,能存放很多大的數據。

垃圾回收機制

程序中,有些數據在使用之後,我們不再需要了,這種數據就稱為垃圾數據。只有回收了垃圾數據,才能釋放內存空間,無法回收或回收不及時的情況,我們就稱為內存洩漏

JavaScript 的垃圾是由垃圾回收器自動回收的。數據存放在“棧空間”和“堆空間”,那垃圾回收器是如何回收的呢?

調用棧中的數據是如何回收的

調用棧在入棧時存儲上下文數據,當某上下文執行完成出棧(pop stack) 後,就銷燬掉了執行上下文,相應的內存空間就會被回收。

調用棧中通過記錄當前執行狀態的指針(稱為 ESP)下移操作,來銷燬執行完畢的函數存在棧中的執行上下文的過程。

堆中的數據是如何回收的

回收堆中的垃圾數據,如要利用 JavaScript 的垃圾回收器。

代際假說和分代收集

代際假說(The Generational Hypothesis)

  • 第一個是大部分對象在內存中存在的時間很短,就是很多對象一經過分配內存,很快就變得不可訪問。
  • 第二個是不死的對象,會活得更久。

V8 中會把堆分為新生代老生代兩個區域,新生代中存放的是生存時間短的對象,老生代中存放的生存時間久的對象。新生代區通常只支持 1~8M 的容量,老生代區則容量大很多。這兩塊區域,V8分別使用了不同的垃圾回收器,以便更高效的實施垃圾回收。

  • 副垃圾回收器,主要負責新生代的垃圾回收。
  • 主垃圾回收器,主要負責老生代的垃圾回收。

垃圾回收器的工作流程

  • 1、標記空間彙總活動對象和非活動對象
  • 2、回收非活動對象所佔據的內存
  • 3、內存整理

副垃圾回收器使用 Scavenge 算法 結合 對象晉升策略

主垃圾回收器使用 標記-清除(Mark-Sweep)算法進行垃圾回收,然後使用標記-整理(Mark-Compact) 進行內存整理。

全停頓

由於垃圾回收器是運行在 JavaScript 主線程上的,單線程的原因,執行垃圾回收算法的時候使得 JavaScript 腳本執行暫停,導致性能下降。為了解決這個問題,V8 將垃圾回收中的標記過程分為一個個的子標記過程,同時讓垃圾回收標記和 JavaScript 應用邏輯交替進行,知道標記階段完成(

增量標記 Incremental Marking 算法)。

編譯器和解析器

編譯型語言 在執行程序之前,需要經過編譯器的編譯過程,並且編譯之後會直接保留機器能讀懂的二進制文件,這樣每次運行程序時,都可以直接運行該二進制文件,而不需要再次重新編譯了。比如 C/C++、GO、Java等。

解釋型語言 在每次運行時都需要通過解釋器對程序進行動態的解釋和執行。比如 Python、JavaScript。


V8工作原理

JS 代碼編譯解析執行過程


V8 依據 JavaScript 代碼生成 AST 和執行上下文,再基於 AST 生成字節碼(編譯),然後通過解釋器執行字節碼,通過編譯器來優化編譯字節碼。

JavaScript 中編譯面向的是全局代碼或函數,比如下載完一個js文件,先編譯這個js文件,但是js文件內定義的函數是不會編譯的,等調用到該函數的時候,JavaScript 引擎才會去編譯該函數。

可以吧 JavaScript 的編譯看成部分:

  • 第一部分從一段 JavaScript 代碼編譯到字節碼,然後解釋器解釋執行字節碼;
  • 第二部分深度編譯,將活躍的字節碼編譯成二進制,然後直接執行二進制。

本文來自:《瀏覽器的工作原理與實踐》極客時間-李兵 的學習筆記記錄


分享到:


相關文章: