iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0

了解完 GraalVM 是一個強大且多功能的虛擬機,能夠運行多種語言,並提供優越的性能和跨平台支持。然而,GraalVM 不僅僅是在 JVM 基礎上進行的優化,它還提供了一些突破性的技術,讓開發者能更靈活地部署和運行應用程序。

其中,Native Image 就是 GraalVM 的一個核心功能。當我們談到 GraalVM 的靈活性和性能優勢時,Native Image 提供了一條全新的路徑,將 Java 程式碼編譯成為原生二進制文件,不再依賴 JVM 運行。這帶來了許多傳統 Java 開發所無法實現的性能提升和運行優勢。

接下來,我們將介紹探討Native Image的運作方式與優勢。

關於Native Image

Native Image 是一項將 Java 程式碼提前編譯成二進制文件(即原生可執行文件)的技術。與傳統的 Java 虛擬機器(JVM)運行方式不同,Native Image 僅包含在運行時所需的代碼,例如應用程序類、標準庫類、語言運行時以及來自 JDK 的靜態鏈接原生程式碼。

這種由 Native Image 編譯生成的可執行文件有以下幾個關鍵優點:

  • 使用的資源僅為 Java 虛擬機器的一小部分,因此運行成本較低。
  • 啟動速度極快,僅需毫秒級。
  • 無需暖機,立即達到最佳性能。
    • 在資料庫系統中,可能需要先執行一些查詢來填充緩存,這個過程就可以稱為"暖機"。
  • 可以打包成輕量級的容器映像,適合快速、高效的部署。
  • 減少了潛在的攻擊面。
    • 程式變得更安全了。因為Native Image只包含必要的程式碼,所以減少了可能被攻擊者利用的部分。

Native Image如何運作

Graal編譯器除了可以即時編譯(JIT),還可以預先編譯(AOT),產生原生執行檔。考慮到Java的動態特性,這是怎麼做到的呢? 下圖為GraalVM Native Image 的運作方式

https://ithelp.ithome.com.tw/upload/images/20240910/20115895XddDTI2Xfu.png

GraalVM Native Image 透過靜態分析(Points-to Analysis)來判斷應用程式在運行時所需的類別和方法,並將這些程式碼在構建階段進行提前編譯(AOT)。同時,它會預先初始化一些物件並進行堆快照,將這些物件的狀態寫入映像檔,讓應用程式在啟動時可以快速載入。最終,生成的原生執行檔不需要 JVM 支援,並依靠 Substrate VM 提供輕量的執行時環境來管理記憶體與執行緒。這種方式使應用程式體積更小,啟動更快。

綠色區塊代表 GraalVM Native Image 中關鍵的三個運作步驟,這些步驟會反覆進行,直到達到「固定點」(fixed point),即所有的程式碼路徑和初始化物件都被處理完畢。具體來說:

  1. Points-to Analysis(指向分析)
    這個步驟會分析應用程式中哪些類別、方法和欄位會在執行時被使用到。它從應用的進入點開始,例如 main 方法,逐步遞歸分析程式碼,找出所有間接可達的路徑。這個過程會持續,直到所有需要的程式碼都被分析完畢。這個分析確保生成的原生執行檔只包含必要的程式碼,避免多餘部分,減小執行檔的大小。
  2. Run Initializations(初始化執行)
    在構建過程中,Native Image 預設會在執行時初始化類別,但如果分析結果證明某些類別或物件可以安全地在構建階段初始化,它們就會在構建時被初始化,避免在應用執行時做這些初始化操作。這樣可以減少應用啟動時的負擔,進一步加快啟動速度。
  3. Heap Snapshotting(堆快照)
    當應用程式執行初始化時,某些物件會被配置到堆記憶體中。Native Image 會在構建階段捕捉這些初始化的物件,並將它們的狀態寫入映像檔中的堆區。這樣,應用程式啟動時,可以直接載入這些預配置的物件,減少重新配置的時間,從而大幅提升啟動效能。

這三個步驟是反覆運行的,因為指向分析的結果會影響初始化和堆快照的過程,而堆快照的結果又可能改變指向分析的內容。這個反覆過程會進行多次,直到所有程式碼路徑和初始化物件都被分析並處理完全,這時即達到「固定點」,可進入下一步的編譯生成階段。

接著從綠色區塊往右邊,這部分描述了生成原生可執行檔的兩個關鍵步驟:

  • Ahead-of-Time Compilation(AOT 編譯)
    當指向分析(Points-to Analysis)和初始化步驟完成後,GraalVM 會將所有已確認會在執行時使用的程式碼進行提前編譯(AOT)。這個編譯過程是將 Java bytecode 編譯成與目標平台相容的機器碼。編譯後的程式碼會被存放在可執行檔的 Code in Text Section(文字區段中的程式碼部分),這是程式的主體邏輯。由於是提前編譯,因此在執行時不需要即時編譯(JIT),這減少了執行時的延遲,讓應用啟動速度更快。

  • Image Heap Writing(映像堆寫入)
    在堆快照(Heap Snapshotting)完成後,應用程式的初始記憶體狀態會被寫入到映像檔中。這些預先配置的物件和數據會被存放在 Image Heap in Data Section(資料區段中的映像堆部分)。當應用程式啟動時,它會直接載入這些記憶體數據,不需要重新初始化堆中的物件,進一步加快了啟動速度。

這個過程的最終結果是生成一個 原生可執行檔,該執行檔是平台專屬的(根據你選擇的目標平台編譯),且不需要 Java 虛擬機(JVM)來運行。這個原生可執行檔包含了應用程式的所有必需程式碼和初始化好的記憶體狀態,既輕量又高效,並能提供更快的啟動時間與更低的資源消耗。

Native Image 如何幫助 Quarkus 開發?

了解 Native Image 的運作對 Quarkus 開發非常有幫助,因為 Quarkus 的核心特性之一就是其對 GraalVM 的原生支援。透過 Native Image,Quarkus 應用程式能夠顯著提升效能,特別是在以下幾個方面:

  1. 快速啟動:因為程式碼已經提前編譯、初始化物件已經在構建時準備好,Quarkus 應用啟動速度極快,特別適合雲端環境中的短生命週期容器。
  2. 低記憶體占用:Native Image 移除了不必要的程式碼與函式庫,並且只包含應用程式真正需要的部分,這大幅減少了記憶體的占用,非常適合在資源有限的環境中運行。
  3. 高效的原生執行:因為 Native Image 不需要 JVM 支援,它能在原生平台上直接執行,運行效能更高,且可以在無伺服器架構(如 GCP Cloud Run)中充分發揮性能優勢。
  4. 更小的部署單元:Native Image 生成的原生可執行檔是獨立的,不需要附帶 JVM 或其他依賴,這使得部署包更小,簡化了分發和運維的工作。

因此,理解和善用 Native Image 的運作原理,對於 Quarkus 開發者來說,能夠充分發揮 Quarkus 在啟動速度、資源效率和運行效能上的優勢,使其成為在微服務、容器化應用或雲原生環境中強大的開發工具。

常見的 Native Image 相關問題:

在開發雲原生應用程式時,使用 GraalVM Native Image 雖然可以顯著降低應用的記憶體使用量並提升啟動速度。但由於 Native Image 採用了靜態編譯方法,與傳統 JVM 相比,開發者在使用過程中會遇到一些不同的挑戰和問題。以下是一些常見的 Native Image 相關問題及其解決方案:

  1. 長時間的構建過程

    Native Image 構建過程比普通 JVM 編譯要慢得多,尤其是當應用程式規模較大時,編譯時間會顯著增長。這是因為 GraalVM 進行了大量的靜態分析和優化。

    解決方案:利用 Quarkus 提供的快速開發模式,開發過程中先以 JVM 模式進行開發,僅在需要時使用 Native Image 編譯。可以通過 CI/CD 管道進行自動編譯,將這部分負擔轉移到後端。

  2. 第三方函式庫相容性問題
    並非所有的 Java 函式庫都與 Native Image 相容,特別是那些依賴於反射、動態代理或特定 JVM 特性的函式庫。

    解決方案:選擇已知與 GraalVM Native Image 相容的函式庫,或尋找替代方案。此外,檢查函式庫的官方文件,了解它們是否提供 Native Image 支援。

  3. 動態特性支援不足

    Java 的動態功能(例如反射、動態代理、序列化等)在 Native Image 中支援有限。由於 Native Image 使用靜態分析來確定可達程式碼,動態加載的類別可能會被排除。開發者需要使用特定的配置文件來告知 Native Image 需要保留這些類別和方法。

    解決方案:需要手動配置 reflect-config.json 或使用 Quarkus 的 @RegisterForReflection 註解來明確告知哪些類別需要在反射中使用。

為什麼 Native Image 需要安裝 C++ 編譯器?

Native Image 的生成過程需要直接生成與作業系統和硬體相容的機器碼,這部分需要 C++ 編譯器來處理,特別是在系統呼叫、記憶體管理等底層功能上。而JVM 則內建了許多像是記憶體管理、垃圾回收、執行緒管理等功能,它們全由 JVM 自己處理,因此不需要依賴外部的 C++ 編譯器來生成原生機器碼。JVM 處理所有 Java bytecode 的解釋和運行,負責與作業系統交互。


上一篇
GraalVM快速理解-GraalVM
下一篇
Hello Quarkus GraalVM
系列文
微服務奇兵:30天Quarkus特訓營13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言