iT邦幫忙

2024 iThome 鐵人賽

DAY 7
3

關於JVM與GraalVM,此篇稍微紀錄一下GraalVM研讀。不過在理解GraalVM之前須先理解何謂JVM。了解JVM才有辦法理解GraalVM與他的差異之處。

關於JVM

我們要在機台上跑Java Code,一般都需要安裝設置JVM。JVM是一個虛擬機器,它讓Java程式能夠在不同的操作系統和硬體架構上執行。Java以「一次編寫,隨處執行」為理念,這是因為JVM將Java的bytecode翻譯為機器的指令。JVM不僅能運行Java,還支援許多語言如Kotlin、Scala、Groovy等。

要讓Java程式在任何設備上執行,流程如下:

.java → javac → .class(bytecode) → JVM → 機器碼 → 執行

這個流程代表從程式碼經過編譯產生bytecode,JVM會將這些bytecode翻譯成機器能夠理解的指令來執行。JVM的核心功能即是透過Interpretation或JIT Compilation來完成這個任務。

  • Interpretation : JVM 會逐行讀取 bytecode,並逐行將每個指令翻譯成相應的機器語言,再交由處理器執行。
  • JIT Compilation : JIT 是在程式運行時,將常用的 bytecode 片段轉換為機器語言,並進行優化,以加速後續的執行。也就是說,當 JVM 偵測到某些程式碼頻繁被執行時,它會將這些部分直接轉換成機器語言,這樣後續的執行不再需要每次都解釋,能大幅提升效能。

架構概念

基本上對於Java開發者,較鮮少往JVM的架構去研讀,一般都是JRE安裝好後,確定Java Code能運行就止住了。但要深入了解GraalVM,建議對於JVM是需要一定的認知的。但這區塊會比較偏向編譯器和運行時系統的底層實現,基本上只要過一下大概即可。

會focus在最底層的編譯部分(Execution Engine),後面會對這一塊多加解釋

下圖為JVM的架構概念圖,

基本上分三個區塊

  • Class Loader(類別加載器)

    類別加載器負責將磁碟上的 .class 檔案(即編譯好的bytecode)載入JVM的記憶體中。這個過程包括了:

    • 類別檢驗(Class Verification):類別檢驗是類別加載流程的第一步,用來保證載入的 .class 檔案是正確且不會破壞JVM的安全性或穩定性。JVM透過這個過程來確認:

      • 檔案格式檢驗:檢查 .class 檔案是否符合Java Class File的結構規範,例如是否有正確的標頭(magic number)、版本號是否支援、是否包含正確的常量池和正確的欄位/方法資訊。
      • 位元碼檢驗:確保類別中的Java bytecode符合JVM規範。例如,操作碼是否有效,操作數是否正確,跳轉指令是否指向正確的邊界等。
      • 語義檢驗:檢查類別和方法的語法規則。例如,確保方法的回傳值和參數型別正確,確保類別中使用的繼承、實作關係是合法的。
      • 符號檢驗:檢查引用的類別是否正確且可用,並確認引用的變數、方法、常量等是否存在於被引用的類別或介面中。

      這個步驟的主要目的是保證bytecode在執行時不會違反Java語言的規範和JVM的執行規則,從而確保Java應用的安全性和穩定性。

    • 類別解析(Class Resolution)

      類別解析的主要任務是將.class檔案中使用的符號引用(Symbolic Reference)轉換為JVM的直接引用(Direct Reference)。符號引用是指在編譯階段,用文字描述的引用關係,例如某個變數或方法的名稱。直接引用則是指記憶體中的真實位置,這樣JVM可以快速存取變數或方法。

      這個步驟包括以下內容:

      • 欄位解析:將類別中的欄位符號(例如變數名稱)解析為實際記憶體位址,讓JVM可以直接讀取或寫入這些變數。
      • 方法解析:將方法的符號名稱(例如方法的名稱和簽名)解析為實際的方法記憶體位址,這樣執行時可以快速跳轉到正確的方法執行。
      • 類別和介面解析:將符號化的類別名稱和介面名稱解析為JVM記憶體中的具體類別和介面結構,這讓類別之間的繼承和實作關係可以正確建立。
    • 類別初始化(Class Initialization)

      類別初始化是類別加載的最後一個步驟,這個步驟負責執行類別的靜態程式碼和初始化靜態變數。它的主要任務包括:

      • 靜態變數初始化:對類別中宣告的靜態變數進行賦值。例如,若一個類別有靜態變數static int a = 5;,那麼在初始化時,JVM會為這個變數分配記憶體並賦值為5。
      • 靜態程式區塊執行:如果類別包含靜態程式碼區塊(static { ... }),這些程式碼會在初始化階段被執行,通常用來進行類別層級的初始化操作。例如,可能會初始化某些靜態資源或執行一些一次性的設置操作。
  • JVM 記憶體區域

    JVM內部分成幾個重要的記憶體區域,分別用於不同的目的:主要職責:存放 JVM 運行時所需的資料,例如類別的資訊、物件、執行緒棧等

    • Method Area: 這裡存放類別結構相關的信息,如運行時常量池(Runtime Constant Pool)、字段(Fields)和方法數據(Method Data)、構造方法(Constructors)等。
    • Heap : 這是JVM中最主要的記憶體區域,所有的物件和陣列都在這裡分配記憶體。
    • Stack : ****每個執行緒都有自己的Java Stack,用來儲存該執行緒的區域變數、方法參數和方法調用記錄。每個方法的調用對應一個「stack frame」。
    • PC Registers : 每個執行緒都有自己的PC Register,用來保存當前執行的Java方法的bytecode指令地址。
    • Native Method Stacks: 與JVM Language Stacks相似,這裡保存非Java語言的方法,如C、C++,這是JVM和本地系統互動的橋樑。
  • 執行與原生互動層

    是JVM的核心部分,負責實際執行Java的bytecode並處理JVM與本地系統之間的互動。

    • Execution Engine(執行引擎):

      執行JVM內的bytecode。它可以直接解釋執行bytecode,或通過即時編譯器(JIT Compiler)將bytecode轉換為機器碼後執行。執行引擎的設計目的是優化Java程式的運行效能。

      • 直譯執行 : 逐條直譯並執行bytecode,這種方式在程式初次運行時較慢,但實現靈活。
      • JIT編譯 : 當程式中的某段程式頻繁被執行時,JIT編譯器會將這段bytecode轉換為直接的機器碼,這樣後續運行時可以節省重複直譯的時間,提升效能。
    • Native Method Interface(原生方法介面):

      JVM透過JNI與本地程式碼進行互動,使得Java程式可以呼叫非Java程式語言撰寫的函式,例如C或C++撰寫的本地函式。

    • Native Method Libraries(原生方法函式庫)

      這些函式庫包含使用非Java語言(如C或C++)實作的函式,Java程式可以透過JNI呼叫這些函式來與本地系統互動。

https://ithelp.ithome.com.tw/upload/images/20240908/20115895g14FI3vPcJ.png

上述JVM解析內容 可以被視為一個框架實作參考,它是一套標準的執行環境,用來運行 Java 和其他 JVM 兼容語言的程式。開發者可以專注在編寫和運行程式,不用關心底層的硬體和作業系統。而這個框架本身並不依賴於特定的硬體或作業系統,是通過實現虛擬化層來抽象底層的細節,使得應用程式能夠跨平台運行。

實作 JVM 的方式有很多,不同的 JVM 實作可能會針對特定需求進行優化。常見的 JVM 實作包括:

  • HotSpot JVM(Oracle):最常見的 JVM 實作,針對高效能應用進行了深度優化,並支持即時編譯(JIT)技術 。
  • OpenJ9(IBM):專注於節省內存使用和啟動時間的 JVM 實作,適合雲端和資源受限的環境。
  • GraalVM:一個多語言虛擬機,除了傳統 JVM 功能,還能夠將 Java 應用編譯成原生可執行檔,提升啟動速度和效能。

不同的實作方式允許開發者根據應用場景的需求,選擇最適合的 JVM 版本來獲得最佳性能。


上一篇
Groovy 語法簡易介紹 與 Gradle 相關設定檔案
下一篇
GraalVM快速理解-GraalVM
系列文
微服務奇兵:30天Quarkus特訓營30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言