iT邦幫忙

2024 iThome 鐵人賽

DAY 10
1

接著我們回到前幾章介紹的 Hello Quarkus 專案,這次我們將其執行在 GraalVM 環境下。如果你使用的是 Windows 系統,設置起來可能會稍微複雜一些,也可能會遇到各種問題。這裡建議使用 WSL2(Windows Subsystem for Linux 2) 來運行該專案,能夠簡化過程並減少問題。關於 WSL2 的安裝,可以參考 微軟官方文件

Hello Quarkus GraalVM

1. 環境設定

在前幾章中我們已經介紹過環境設定,這裡我們將構建指令修改如下,讓專案運行在 Native Image 模式下:

./gradlew clean build '-Dquarkus.package.type=native'

如果尚未安裝 GraalVM 的 Native Image 工具,請使用以下指令安裝:

gu install native-image

接著,確認 native-image 是否安裝成功,可以用以下指令檢查版本:

native-image --version

2. 構建過程中的優化訊息

專案建置完成後,終端機會顯示一些構建資訊,如下圖所示。這些訊息包含了性能優化建議,能幫助我們進一步提升效能:

https://ithelp.ithome.com.tw/upload/images/20240911/20115895sjItxJvAYK.png

  • Top 10 Origins of Code AreaTop 10 Object Types in Image Heap:這兩部分展示了生成原生映像過程中,程式碼區域和記憶體堆中的占用情況。例如,像 java.baseio.netty 這些常用的類庫佔用較多的空間。

  • Recommendations: 系統將提供兩個主要的優化建議:

    • HEAP: 建議調整最大堆積記憶體大小,以增強系統穩定性和效能預測性。
    • CPU: 使用 march=native 這個選項來啟用更多的 CPU 特性,進而提升運行效率。
  • Produced Artifacts: 顯示構建過程生成的工件,包含 build-info 檔案和可執行文件。

3. 執行 Native Image

專案構建完成後,可以到 build 資料夾下,執行生成的可執行檔,例如:

./hello-quarkus-1.0-SNAPSHOT-runner

成功啟動後,你會看到類似以下的結果畫面。

https://ithelp.ithome.com.tw/upload/images/20240911/20115895KQYaJDCMbY.png

4. 性能對比:JVM vs GraalVM

接著,我們來稍微比較一下 JVM 和 GraalVM 在啟動時間上的差異:

  • JVM:啟動時間約為 1.055s
  • GraalVM Native Image:啟動時間僅為 0.383s

可以看出,GraalVM Native Image 的啟動速度大幅快於 JVM

https://ithelp.ithome.com.tw/upload/images/20240911/201158956G3v4Rz8Qk.png

5. 記憶體與效能調校

在生成 Native Image 時,雖然啟動時間顯著減少,但我們仍然需要根據應用程式的具體需求進行一些調整,以平衡效能和資源消耗。以下幾個參數可以幫助我們進一步優化 Native Image 的執行效能:

  • 記憶體限制:GraalVM 的 Native Image 默認的記憶體使用量較低,對於某些應用場景可能需要手動調整。你可以使用以下 JVM 引數來限制最大記憶體使用量:
./hello-quarkus-1.0-SNAPSHOT-runner -Xmx256m

這樣可以防止應用程式過度佔用系統資源,確保穩定性。

  • 提前編譯優化:在 Native Image 的構建過程中,我們可以啟用 GraalVM 提供的提前編譯(AOT,Ahead-Of-Time Compilation)技術,通過調整 --report-unsupported-elements-at-runtime--no-fallback 來優化應用程式的二進制文件。
./gradlew build -Dquarkus.native.additional-build-args="--no-fallback --report-unsupported-elements-at-runtime"

使用這些選項可以有效減少最終生成的二進制文件大小,進一步提升效能。

  • 反射使用優化:許多 Java 應用程式依賴反射機制,但反射對於 GraalVM 的 Native Image 來說是一個效能瓶頸。你可以使用 Quarkus 提供的 @RegisterForReflection 註解,告訴 GraalVM 哪些類需要保留反射支援,這樣可以避免過度的資源消耗。
@RegisterForReflection
public class MyEntity {
    // fields and methods
}

6. Docker 中運行 GraalVM Native Image

在生產環境中,將 Native Image 包裝成 Docker 容器是常見的做法。以下是如何構建一個輕量級的 Docker 映像:

首先,在 Dockerfile 中使用 GraalVM 提供的基底映像,並將構建出的 Native Image 包裝其中:

FROM graalvm-ce:latest AS build
COPY target/hello-quarkus-1.0-SNAPSHOT-runner /app/hello-quarkus
CMD ["./app/hello-quarkus"]

執行以下指令來生成 Docker 映像並啟動容器:

docker build -t hello-quarkus-native .
docker run --rm -p 8080:8080 hello-quarkus-native

使用 Docker 部署 Native Image,可以進一步提高應用程式的可攜性和隔離性,適合在微服務架構中使用。

關於GraalVM Native Image 與 JVM 記憶體參數的使用

雖然 GraalVM Native Image 是透過 預先編譯(Ahead-of-Time, AOT) 將 Java 應用程式轉換為原生執行檔案,但它仍然保留了一些 JVM 的設定選項,特別是與記憶體管理相關的參數。在這一章節中,我們將討論一些常見的 JVM 參數如何在 Native Image 中運作,以及這些設定對應用程式效能和資源使用的影響。

-Xmx-Xms 記憶體限制參數

XmxXms 是最常用的 JVM 參數之一,用來控制應用程式執行時的最大堆積記憶體與初始記憶體大小。在 GraalVM 的 Native Image 中,這兩個參數仍然有效,並可用來調整應用程式的記憶體分配。

  • Xmx:設定應用程式執行時的最大堆積記憶體大小。範例如下:

    ./hello-quarkus-1.0-SNAPSHOT-runner -Xmx256m
    

    上述指令限制應用程式最多使用 256 MB 的記憶體,這對於生產環境中記憶體資源有限的情況(例如容器化部署)非常有用。

  • Xms:設定應用程式啟動時的初始堆積記憶體大小。範例如下:

    ./hello-quarkus-1.0-SNAPSHOT-runner -Xms128m
    

    這樣可以在應用程式啟動時,直接分配 128 MB 的記憶體,有助於提升啟動效能,特別是在應用程式需要立即使用較大記憶體的情況下。

其他 GraalVM 支援的 JVM 記憶體參數 (官方參考)

除了 -Xmx-Xms 之外,還有一些記憶體管理相關的 JVM 參數可以在 Native Image 中使用:

  • XX:MaxDirectMemorySize:設定直接記憶體的最大值。這個參數對於使用 NIO 或 Netty 這類框架的應用程式非常重要,因為它們大量使用直接記憶體。範例如下:

    ./hello-quarkus-1.0-SNAPSHOT-runner -XX:MaxDirectMemorySize=128m
    

    這樣可以限制應用程式最多使用 128 MB 的直接記憶體。

  • XX:+UseG1GC:雖然 Native Image 不再依賴傳統的 JVM 垃圾回收機制,但在某些情況下仍然可以啟用特定的垃圾回收策略來進行記憶體管理。然而,這在 Native Image 中較少見。

為何 GraalVM Native Image 保留部分 JVM 參數?

在 GraalVM 中,Native Image 是透過 AOT 編譯產生的原生執行檔案,理論上不需要 JVM 層級的運行環境。然而,為了提供與傳統 JVM 模式相似的調整能力,GraalVM 仍然允許使用部分 JVM 參數來控制堆積記憶體大小、直接記憶體及垃圾回收等功能。這使開發者能夠無縫地從 JVM 運行模式過渡到 Native Image 模式,並能精確控制應用程式的資源使用。

設定記憶體參數的必要性

大多數應用程式的記憶體管理是關鍵因素之一。在高效能的生產環境中,我們希望應用程式能夠在固定的資源配置下運行,且不會因記憶體不足而崩潰。這就是為何在構建和運行 Native Image 時,我們仍需調整記憶體參數來滿足不同場景需求。常見的應用場景包括:

  • 微服務架構:在微服務環境中,每個服務都運行在自己的容器中。通過設定 Xmx,可以限制每個服務的記憶體使用,確保系統整體資源不會被某個服務過度佔用。
  • 無伺服器(Serverless)架構:在無伺服器架構中,應用程式需要快速啟動且使用最少的資源。這時可以通過 XmxXms 來限制記憶體並加快啟動速度。
  • 高效能批次處理:某些批次處理應用程式在執行時會佔用大量記憶體,這時可以使用 Xmx 來限制記憶體使用,避免系統過載。

小記

在前面我們介紹了如何使用 GraalVM 在 Quarkus 專案中生成並執行 Native Image,雖然 Native Image 提供了優秀的效能,但對於開發者來說,這種模式並不如 JVM 那麼友善。

首先,開發工具的支援仍不夠完善。許多 Java 開發時常用的工具與框架在 Native Image 的環境下並沒有完整的支援,這讓開發過程變得更複雜。其次,編譯時間較長 是另一個顯著的挑戰。Native Image 的生成過程需要進行繁瑣的靜態編譯,這相較於 JVM 下的即時編譯(JIT)而言,開發者在每次構建或調試時都必須等待更長的時間,這可能會延長開發週期。此外,Native Image 中一些常見的動態功能(如反射和動態類加載)需要手動配置,這進一步增加了開發難度。

因此,雖然 Native Image 提供了更好的運行效能,但在開發階段,開發者往往需要面對工具不足、長編譯時間以及更多的配置工作。

尤其是windows系統,設置上遇到蠻多問題點的,所以建議可以在WSL2去實驗這些。也不用太糾結一定要在Windows下做實驗,因為後續做是做容器服務。

常用指令總結

這裡總結一些常用指令,特別針對 GraalVM 和 Native Image 開發環境,讓大家更方便運行和測試專案:

  • 構建 Native Image:使用 GraalVM 建構專案為 Native Image:

    
    ./gradlew clean build '-Dquarkus.package.type=native'
    
  • 啟動 Native Image:運行建構完成的 Quarkus 應用:

    
    ./your-project-name-runner
    
  • 開發模式(JVM 模式):在 JVM 模式下開發,支援即時重載:

    ./gradlew quarkusDev
    
  • 清理專案:移除 buildtarget 資料夾中的建構檔案:

    ./gradlew clean
    
  • 構建 JVM 模式專案:建構專案為可執行的 JAR 檔案:

    ./gradlew build
    
  • 運行測試:運行專案測試,確保代碼通過所有測試:

    ./gradlew test
    
  • 列出所有 Gradle 任務:查看專案中所有可用的 Gradle 任務:

    ./gradlew tasks
    
  • 運行 Native Image 測試:在 Native Image 環境中運行測試:

    ./gradlew test '-Dquarkus.package.type=native'
    
  • 啟用文件系統監控:增量建構時自動檢測文件變更:

    ./gradlew clean build '-Dquarkus.package.type=native' -Dorg.gradle.vfs.watch=true
    

上一篇
Native Image
下一篇
VisualVM 監控工具
系列文
微服務奇兵:30天Quarkus特訓營30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言