位元詩人 用 GraalVM 編譯原生 Java 程式

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

本文展示如何使用 GraalVM 將 Java 程式編譯為原生映像檔(Native Image)的完整流程。

核心優勢與適用場景

將 Java 程式編譯成原生映像檔後,能顯著縮減程式體積並達到極速的冷啟動時間。這項技術特別適合部署在以下情境:

  • 微服務與後端 API(Backend Services)
  • 無伺服器架構(Serverless / FaaS)
  • 命令列工具(CLI Tools)

💡 開發心法:平時開發時,不需每次調整代碼都編譯成原生映像檔,避免因編譯時間較長而降低開發效率。建議維持原有的快速迭代節奏,直到最後發佈前再進行原生編譯

不過,當專案 引入新的第三方相依性(Dependencies) 時,建議先測試編譯一次,提早確認該套件是否支援 GraalVM。

系統需求

JDK 基礎

  • GraalVM:請安裝對應您 Java 版本的 GraalVM 發行版。

C / C++ 編譯器(環境必備)

GraalVM 在進行原生編譯時,底層需要系統內建的 C 編譯器支援:

  • Windows:需安裝 Visual Studio Build Tools(可僅勾選「使用 C++ 的桌面開發」工作負載,無需安裝完整的 Visual Studio IDE)。
  • Linux / macOS:需安裝 GCCClang(Ubuntu 可透過套件管理員安裝 build-essential)。

建構工具(非必備,推薦使用)

  • Gradle / Maven:雖然不是硬性規定,但強烈建議使用。在處理第三方套件相依性與自動化原生編譯時,能大幅簡化設定流程。

實作演練

Hello World

首先建立一個名為 Main.java 的文字檔案,並撰寫以下內容:

void main()
{
    IO.println("Hello World");
}

編譯為 Class 檔案

在終端機中執行以下指令來編譯 Java 原始碼:

javac Main.java

> 💡 技術提示:此範例使用了 Java 的隱式宣告類別(Implicit Classes)新特性。若您使用的是支援此特性的 JDK 版本,編譯與執行時可能需加上 --enable-preview 參數。若使用傳統寫法,則依標準方式編譯即可。

開啟對應的終端機環境

進行原生編譯前,請務必切換至正確的環境:

  • Windows 系統:請從開始功能表開啟 Developer Command Prompt for VS(開發人員命令提示字元)。必須在此環境下操作,GraalVM 才能正確調用 C++ 工具鏈。
  • Linux / macOS 系統:直接使用系統自帶的標準 Terminal 即可。

編譯為原生映像檔(Native Image)

在上述對應的終端機中,執行以下指令進行原生編譯:

native-image Main

編譯完成後,系統會生成可執行檔:

  • Windows:生成 main.exe
  • Linux / macOS:生成 main

Gradle 專案整合

在實際開發中,我們通常會引入第三方套件。使用 Gradle 搭配 Shadow Jar 來自動管理相依性並打包成 Fat Jar / Uber Jar,是最推薦的實作方式。

建立並初始化專案

首先,在終端機中建立專案目錄並初始化:

mkdir myapp
cd myapp
gradle init

> 💡 提示:初始化過程中,請選擇 Application 專案類型,並將實作語言設定為 Java

配置 build.gradle

打開專案中的 build.gradle 檔案,引入 Shadow Jar 外掛,並指定程式的進入點(Main-Class):

plugins {
    id 'java'

    // 引入 Shadow Jar 外掛,用於打包包含所有相依套件的 Fat Jar
    id 'com.gradleup.shadow' version '8.3.5'
}

shadowJar {
    manifest {
        // 請將此處替換為您專案實際的 Main Class 路徑
        attributes 'Main-Class': 'org.example.App'
    }
}

建構 Fat Jar

執行以下指令,將專案源碼與所有第三方套件打包成單一的 Jar 檔:

./gradlew shadowJar

打包完成後,您會在 build/libs/ 目錄下找到產出的 app-all.jar 檔案。

將 Jar 編譯為原生映像檔

最後,利用 GraalVM 的 native-image 指令,直接對該 Jar 檔進行原生編譯:

native-image -jar build/libs/app-all.jar

🎯 為什麼推薦使用建構工具?

透過 Gradle 處理自動化建構,我們不需要手動管理繁雜的類別路徑(Classpath)與相依套件。開發者只需專注於編寫業務邏輯,最後一步交給 Shadow Jar 與 GraalVM,即可輕鬆產出高效能的原生執行檔。

潛在限制與開發考量

雖然 GraalVM 帶來了極高的效能優勢,但在開發時仍需注意 AOT(Ahead-of-Time)編譯的架構限制:

反射機制(Reflection)的挑戰

傳統 Java 依賴動態特性,而 AOT 編譯則需要在編譯期就確定所有的代碼執行路徑。因此,如果程式或第三方套件中大量使用了反射(Reflection)動態代理(Dynamic Proxies)動態類別載入,GraalVM 在編譯時可能會因為找不到進入點而失敗。

雖然可以透過配置檔告知編譯器,或將特定類別的初始化時機延後到執行期(Runtime Initialization),但如果過度依賴執行期處理,就會削弱 AOT 編譯原本帶來的快速啟動與機械碼優化。

生態系的選擇策略

為了確保專案能順利編譯為原生映像檔,無論是自行撰寫代碼還是挑選第三方函式庫(Libraries)時,都應優先選擇 對 GraalVM 友善(GraalVM-friendly) 的方案:

  • 優先挑選內建支援 AOT 編譯的現代框架(如 Spring Boot 3.x、Quarkus、Micronaut)。
  • 引入新的開源套件時,建議先查閱其官方文件是否支援 Native Image 運作。
關於作者

位元詩人 (ByteBard) 是資訊領域碩士,喜歡用開源技術來解決各式各樣的問題。這類技術跨平台、重用性高、技術生命長。

除了開源技術以外,位元詩人喜歡日本料理和黑咖啡,會一些日文,有時會自助旅行。