構(gòu)建第一個GraalVM應(yīng)用鏡像,體驗毫秒級極速啟動!
介紹
GraalVM使用其Ahead-Of-Time(AOT)編譯器將Java應(yīng)用程序編譯為機器可執(zhí)行文件。這些可執(zhí)行文件可以直接在目標(biāo)機器上執(zhí)行,而無需使用即時編譯器(JIT)。GraalVM生成的二進制文件體積較小,啟動速度快,并且在沒有任何預(yù)熱的情況下提供最佳性能。此外,這些可執(zhí)行文件的內(nèi)存占用和CPU使用率低于在JVM上運行的應(yīng)用程序。
Docker允許我們將軟件組件打包成Docker鏡像,并作為Docker容器運行。Docker容器包含應(yīng)用程序運行所需的一切,包括應(yīng)用代碼、運行時、系統(tǒng)工具和庫。
在本文中,我們創(chuàng)建一個Java應(yīng)用程序的GraalVM原生鏡像,并將其作為Docker容器運行。
什么是原生鏡像?
原生鏡像是一種將Java代碼提前編譯成本地可執(zhí)行文件的技術(shù)。這個本地可執(zhí)行文件只包含在運行時需要執(zhí)行的代碼,包括應(yīng)用程序類、標(biāo)準(zhǔn)庫類、語言運行時以及來自JDK的靜態(tài)鏈接的本機代碼。
原生鏡像構(gòu)建器(native-image)掃描應(yīng)用程序類和其他元數(shù)據(jù),來創(chuàng)建特定于操作系統(tǒng)和架構(gòu)的二進制文件。native-image工具對應(yīng)用程序代碼進行靜態(tài)分析,確定在應(yīng)用程序運行時可達到的類和方法。然后,它將所需的類、方法和資源編譯成一個二進制可執(zhí)行文件。
原生鏡像的好處
原生鏡像可執(zhí)行文件具有以下幾個優(yōu)點:
- 由于原生鏡像構(gòu)建器僅編譯運行時所需的資源,因此可執(zhí)行文件較小。
- 原生可執(zhí)行文件具有非??斓膯訒r間,因為它們在目標(biāo)機器上直接執(zhí)行,而無需使用JIT編譯器。
- 由于只打包所需的應(yīng)用程序資源,提供了較小的被攻擊面。
- 適用于打包到輕量級容器鏡像(例如Docker鏡像)中,以實現(xiàn)快速高效的部署。
構(gòu)建GraalVM原生鏡像
在本節(jié)中,我們將為一個Spring Boot應(yīng)用程序構(gòu)建一個GraalVM原生鏡像。首先,需要安裝GraalVM并設(shè)置JAVA_HOME環(huán)境變量。其次,創(chuàng)建一個帶有Spring Web和GraalVM原生支持依賴的Spring Boot應(yīng)用程序:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.4</version>
</dependency>
還需要添加以下插件以支持GraalVM原生鏡像:
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.27</version>
</plugin>
</plugins>
</build>
該應(yīng)用程序包含一個REST Controller 示例:
@RestController
class HelloController {
@GetMapping
public String hello() {
return "Hello GraalVM";
}
}
使用Maven命令構(gòu)建原生可執(zhí)行文件:
$mvn -Pnative native:compile
使用native-maven-plugin構(gòu)建GraalVM原生鏡像。由于GraalVM原生鏡像編譯器執(zhí)行靜態(tài)代碼分析,與常規(guī)的Java應(yīng)用程序編譯相比,構(gòu)建時間較長。
以下是GraalVM編譯的輸出示例:
========================================================================================================================
GraalVM Native Image: Generating 'springboot-graalvm-docker' (executable)...
========================================================================================================================
<strong>[1/8] Initializing... (42.7s @ 0.15GB)</strong>
Java version: 17.0.8+9-LTS, vendor version: Oracle GraalVM 17.0.8+9.1
Graal compiler: optimization level: 2, target machine: x86-64-v3, PGO: ML-inferred
C compiler: gcc (linux, x86_64, 11.3.0)
Garbage collector: Serial GC (max heap size: 80% of RAM)
// 省略不重要日志
<strong>[2/8] Performing analysis... [******] (234.6s @ 1.39GB)</strong>
15,543 (90.25%) of 17,222 types reachable
25,854 (67.59%) of 38,251 fields reachable
84,701 (65.21%) of 129,883 methods reachable
4,906 types, 258 fields, and 4,984 methods registered for reflection
64 types, 70 fields, and 55 methods registered for JNI access
4 native libraries: dl, pthread, rt, z
[3/8] Building universe... (14.7s @ 2.03GB)
[4/8] Parsing methods... [*******] (55.6s @ 2.05GB)
[5/8] Inlining methods... [***] (4.9s @ 2.01GB)
[6/8] Compiling methods... [**********
[6/8] Compiling methods... [*******************] (385.2s @ 3.02GB)
[7/8] Layouting methods... [****] (14.0s @ 2.00GB)
[8/8] Creating image... [*****] (30.7s @ 2.72GB)
48.81MB (58.93%) for code area: 48,318 compilation units
30.92MB (37.33%) for image heap: 398,288 objects and 175 resources
3.10MB ( 3.75%) for other data
82.83MB in total
// 省略不重要日志
Finished generating 'springboot-graalvm-docker' in 13m 7s.
// 省略不重要日志
在上述編譯輸出中需要關(guān)注一些關(guān)鍵點,如下:
- 編譯使用GraalVM的Java編譯器來編譯應(yīng)用程序。
- 編譯器對類型、字段和方法進行可達性檢查。
- 然后編譯構(gòu)建原生可執(zhí)行文件,并顯示可執(zhí)行文件的大小和編譯所花費的時間。
- 成功構(gòu)建后,我們可以在目標(biāo)目錄中找到原生可執(zhí)行文件。該可執(zhí)行文件可以在命令行中執(zhí)行。
構(gòu)建Docker鏡像
接下來為前一步生成的原生可執(zhí)行文件開發(fā)一個Docker鏡像。
創(chuàng)建一個Dockerfile:
FROM ubuntu:jammy
COPY target/springboot-graalvm-docker /springboot-graalvm-docker
CMD ["/springboot-graalvm-docker"]
接下來,使用如下命令構(gòu)建Docker鏡像:
$docker build -t springboot-graalvm-docker .
成功構(gòu)建后,可以看到`springboot-graalvm-docker`的Docker鏡像已經(jīng)可以使用了:
$docker images | grep springboot-graalvm-docker
可以使用以下命令執(zhí)行這個鏡像:
$docker run -p 8080:8080 springboot-graalvm-docker
上述命令啟動了容器,Spring Boot的啟動日志如下:
// 省略不重要日志
*** INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization <strong>completed in 14 ms</strong>
*** INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
*** INFO 1 --- [ main] c.b.g.GraalvmDockerImageApplication : Started GraalvmDockerImageApplication in 0.043 seconds (process running for 0.046)
應(yīng)用程序在43毫秒內(nèi)啟動。我們可以訪問REST端點:
$curl localhost:8080
輸出如下:
Hello GraalVM