天天看點

JVM 執行引擎的作用及工作過程

JVM 執行引擎的作用及工作過程
JVM 執行引擎的作用及工作過程

執行引擎的概述

執行引擎是 Java 虛拟機核心的組成部分之一。

“虛拟機”是一個相對于“實體機”的概念,這兩種機器都有代碼執行能力,其差別是實體機的執行引擎是直接建立在處理器、緩存、指令集和作業系統層面上的,而虛拟機的執行引擎則是由軟體自行實作的,是以可以不受實體條件制約地定制指令集與執行引擎的結構關系,能夠執行那些不被硬體直接支援的指令集格式。

JVM 的主要任務是負責裝載位元組碼到其内部,但位元組碼并不能夠直接運作在作業系統之上,因為位元組碼指令并非等價于本地機器指令,它内部包含的僅僅隻是一些能夠被 JVM 所識别的位元組碼指令、符号表,以及其他的輔助資訊。

那麼,如果想讓一個 java 程式運作起來,執行引擎(Execution Engine) 的任務就是将位元組碼指令解釋/編譯為對應平台上的本地機器指令才可以。簡單來說,JVM 中的執行引擎充當了将進階語言翻譯為機器語言的譯者。

JVM 執行引擎的作用及工作過程

執行引擎在執行的過程中究竟需要執行什麼樣子的位元組碼指令完全依賴于 PC 寄存器。

每當執行一項指令操作以後,PC 寄存器就會更新下一條需要被執行的指令位址。

當然方法在執行的過程中,執行引擎有可能會通過存儲在局部變量表中的對象引用準确定位到存儲在 Java 堆區中的對象執行個體資訊,以及通過對象頭中的中繼資料指針定位到目标對象的類型資訊。

從外觀上來看,所有的 Java 虛拟機的執行引擎輸入、輸出都是一緻的:輸入的是位元組碼二進制流,處理過程是位元組碼解釋執行的等效過程,輸出的是執行結果。

Java 代碼編譯和執行過程

JVM 執行引擎的作用及工作過程

大部分的程式代碼轉換成實體機的目标代碼或虛拟機能執行的指令集之前,都需要經過上圖中的各個步驟。

Java 代碼編譯是由 Java 源碼編譯器來完成,流程圖如下所示:

JVM 執行引擎的作用及工作過程

Java 位元組碼的執行是由 JVM 執行引擎來完成,流程圖如下所示:

JVM 執行引擎的作用及工作過程

問題:什麼是解釋器(Interpreter),什麼是 JIT 編譯器?

解釋器: 當 Java 虛拟機啟動時會根據預定義的規範對位元組碼采用逐行解釋的方式執行,将每條位元組碼中的内容“翻譯”為對應平台的本地機器指令執行。

JIT ( Just In Time Compiler) 編譯器: 就是虛拟機将源代碼直接編譯成和本地機器平台相關的機器語言。

問題:為什麼說 Java 語言是半編譯半解釋型語言?

JDK 1.0 時代,将 Java 語言定位為“解釋執行”還是比較準确的,再後來, Java 也發展出可以直接生成本地代碼的編譯器。

現在 JVM 在執行 Java 代碼的時候,通常都會将解釋執行和編譯執行二者結合起來進行。

JVM 執行引擎的作用及工作過程

機器碼、指令、彙編語言

  • 機器碼

各種用二進制編碼方式表示的指令,叫做機器指令碼。開始,人們就用它編寫程式,這就是機器語言。

機器語言雖然能夠被計算機了解和接受,但和人們的語言差别太大,不易被人們了解和記憶,并且用它程式設計容易出差錯。

用它編寫的程式一經輸入計算機, CPU 直接讀取運作,是以和其他語言編的程式相比,執行速度最快。

機器指令與 CPU 緊密相關,是以不同種類的 CPU 所對應的機器指令也就不同。

  • 指令

由于機器碼是有 0 和 1 組成的二進制序列,可讀性實在太差,于是人們發明了指令。

指令就是把機器碼中特定的 0 和 1 序列,簡化成對應的指令(一般為英文簡寫,如 mov,inc 等),可讀性稍好。

由于不同的硬體平台,執行的是同一個操作,對應的機器碼可能不同,是以不同的硬體平台的同一種指令(比如 mov), 對應的機器碼也可能不同。

  • 指令集

不同的硬體平台,各自支援的指令是有差别的。是以每個平台所支援的指令,稱之為對應平台的指令集。如常見的 X86 指令集,對應的是 x86 架構的平台, ARM 指令集,對應的是 ARM 架構的平台。

  • 彙編語言

由于指令的可讀性還是太差,于是人們又發明了彙編語言。

在彙編語言中,用助記符(Mnemonics)代替機器指令的操作碼,用位址符号(Symbol)或标号(Label) 代替指令或操作數的位址。

在不同的硬體平台,彙編語言對應着不同的機器語言指令集,通過彙編過程轉換成機器指令,由于計算機隻認識指令碼,是以用彙編語言編寫的程式還必須翻譯成機器指令碼,計算機才能識别和執行。

  • 進階語言

為了使計算機使用者程式設計更容易些,後來就出現了各種進階計算機語言。進階計算機語言比機器語言、彙編語言更接近人的語言。

當計算機執行進階語言編寫的程式時,仍然需要把程式解釋和編譯成機器的指令碼。完成這個過程的程式就叫做解釋程式或編譯程式。

關于以上所述大緻用圖展示如下:

JVM 執行引擎的作用及工作過程
  • 位元組碼

位元組碼是一種中間狀态的(中間碼)的二進制(檔案),它比機器碼更抽象,需要直譯器轉譯後才能成為機器碼。

位元組碼主要是為了實作特定軟體運作和軟體環境、與硬體環境無關。

位元組碼的實作方式是通過編譯器和虛拟機器。編譯器将原碼編譯成位元組碼,特定平台上的虛拟機器将位元組碼轉譯為可以直接運作的指令,位元組碼的典型應用為 Java bytecode 。

JVM 執行引擎的作用及工作過程

解釋器

JVM 設計者的初衷僅僅隻是單純地為了滿足 Java 程式實作跨平台特性,是以避免采用靜态編譯的方式直接生成本地機器指令,進而誕生了實作解釋器在運作時采用逐行解釋位元組碼執行程式的想法。

解釋器真正意義上所承擔的角色就是一個運作時“翻譯者”,将位元組碼檔案中的内容“翻譯”為對應平台的本地機器指令執行。

當一條位元組碼指令被解釋執行完成後,接着在根據 PC 寄存器中記錄的下一條需要被執行的位元組碼指令執行解釋操作。

在 Java 的發展曆史裡,一共有兩套解釋執行器,即古老的位元組碼解釋器、現在普遍使用的模闆解釋器。

位元組碼解釋器在執行時通過純軟體代碼模拟位元組碼的執行,效率非常低下。

而模版解釋器将每一條位元組碼和一個模闆函數相關聯,模闆函數中直接産生這條位元組碼執行時的機器碼,進而很大程度上提高了解釋器的性能。在 HotSpot VM 中,解釋器主要由 Interpreter 子產品和 Code 子產品組成。

  • Interpreter 子產品:實作了解釋器的核心功能
  • Code 子產品 : 用于管理 HotSpot VM 在運作時生成的本地機器指令。

由于解釋器在設計和實作上非常簡單,是以除了 Java 語言之外,還有許多進階語言同樣也是基于解釋器執行的,比如 python 、Perl 、Ruby 等。但是在今天,基于解釋器執行已經淪落為低效的代名詞,并且時常被一些 C /C++ 程式員所調侃。

為了解決這個問題, JVM 平台支援一種叫做即時編譯的技術。即時編譯的目的是避免函數被解釋執行,而是将整個函數體編譯成為機器碼,每次函數執行時,隻執行編譯後的機器碼即可,這種方式可以時執行效率大幅度提升。

不過無論如何,基于解釋器的執行模式仍然為中間語言的發展做出了不可磨滅的貢獻。

JIT 編譯器

HotSpot VM 是目前市面上高性能虛拟機的代表作之一。它采用解釋器與及時編輯器并行的結構。在 Java 虛拟機運作時,解釋器和即時編譯器能夠互相協作,各自取長補短,盡力去選擇最合适的方式來權衡編譯本地代碼的時間和直接解釋執行代碼的時間。

在今天, Java 程式的運作性能早已脫胎換骨,已經達到了可以和 C/C++ 程式一較高下的地步。

有些開發人員會感覺到詫異,既然 HotSpot VM 中已經内置了 JIT 編譯器了,那麼為什麼還使用解釋器來“拖累”程式的執行性能呢? 比如 JRockit VM 内部就不包含解釋器,位元組碼全部都依靠即時編譯器編譯後執行。

首先明确:當程式啟動後,解釋器可以馬上發揮作用,省去編譯的時間,立即執行。編譯器要想發揮作用,把代碼編譯成本地代碼,需要一定的執行時間。但編譯為本地代碼後,執行效率高。是以:盡管 JRockit VM中程式的執行性能會非常的高效,但程式在啟動時必然會花費更長的時間來進行編譯。對于伺服器來說,啟動時間并非時關注重點,但對于那些看中啟動時間的應用場景而言,或許就需要采用解釋器與及時編譯器并存的架構來換取一個平衡點。在此模式下,當 Java 虛拟機啟動時,解釋器可以首先發揮作用,而不必等待即時編譯器全部編譯完成後在執行,這樣可以省去許多不必要的編譯時間。随着時間的推移,編譯器發揮作用,把越來越多的代碼編譯成本地代碼,獲得更高的執行效率。

同時,解釋執行在編譯器進行激進優化不成立的時候,作為編譯器的“逃生門”。

概念解釋::

Java 語言的“編譯期”其實是一段 “不确定”的操作過程,因為他可能是指一個前端編譯器(其實叫 “編譯器的前端” 更準确一些)把 .java 檔案轉變成 .class 檔案的過程;

也可能是指虛拟機的後端運作期編譯器( JIT 編譯器, Just In Time Compiler )把位元組碼轉變成機器碼的一個過程。

還可能是指使用靜态提前編譯器 ( AOT 編譯器, Ahead Of Time Compiler ) 直接把 .java 檔案編譯成本地機器代碼的過程。

如何選擇:

當然是否需要啟動 JIT 編譯器将位元組碼直接譯為對應平台的本地機器指令,則需要根據代碼被調用執行的頻率而定。關于那些需要被編譯為本地代碼的位元組碼,也被稱之為 “熱點代碼” JIT 編譯器在運作時會針對那些頻繁被調用的“熱點代碼”做出深度優化,将其直接編譯為對應平台的本地機器指令,以此提升 Java 程式的性能。

預設情況下 HotSpot VM 是采用解釋器與即時編譯器并存的架構,當然開發人員可以根據具體的應用場景,通過指令顯示地為 Java 虛拟機指定在運作時到底是完全采用解釋器執行,還是完全采用即時編譯器執行。如下所示:

  • -Xint : 完全采用解釋器模式執行程式;
  • -Xcomp : 完全采用編譯器模式執行程式。如果即時編譯出現問題,解釋器會介入執行。
  • -Xmixed :采用解釋器+即時編譯器的混合模式共同執行程式。