前言
本文展示如何使用 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:需安裝 GCC 或 Clang(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 運作。