Graalvm 替代 JVM 真的可以帶來巨大的性能優(yōu)勢嗎?
介紹
Spring Boot有助于輕松開發(fā)獨立的、可用于生產(chǎn)的 Spring 應(yīng)用程序。它對 Spring 平臺和第三方庫采用固執(zhí)己見的方法:以最少的配置簡化設(shè)置過程。優(yōu)勢:
- 易于使用:Spring Boot 簡化了獨立 Spring 應(yīng)用程序的創(chuàng)建,無需復(fù)雜的配置。
- 嵌入式服務(wù)器:它允許直接嵌入 Tomcat、Jetty 或 Undertow 等服務(wù)器,從而無需單獨部署 WAR 文件。
- Starter 依賴項:Spring Boot 提供預(yù)配置的“starter”依賴項,降低了構(gòu)建配置的復(fù)雜性。
- 自動配置:Spring Boot 自動配置 Spring 和第三方庫,最大限度地減少手動設(shè)置工作。
- 生產(chǎn)就緒功能:它提供生產(chǎn)就緒功能,例如指標、運行狀況檢查和外部化配置,確保應(yīng)用程序穩(wěn)健且可擴展。
- 無需代碼生成或 XML 配置:Spring Boot 運行時無需生成任何代碼,無需 XML 配置文件,從而提高了開發(fā)效率。
在常見的部署中,用 Java 編寫的 Spring Boot 應(yīng)用程序被編譯成默認在 Java 虛擬機 (JVM) 中運行的字節(jié)碼。還有另一種鮮為人知的運行 Java 應(yīng)用程序的方式:Native application
GraalVM通過提前將 Java 應(yīng)用程序編譯成緊湊的獨立二進制文件,徹底改變了 Java 應(yīng)用程序。這些二進制文件展現(xiàn)出明顯的優(yōu)勢,啟動速度比傳統(tǒng) Java 應(yīng)用程序快近 100 倍。它們無需預(yù)熱即可提供峰值性能,同時與 Java 虛擬機 (JVM) 同類產(chǎn)品相比,消耗的內(nèi)存和 CPU 資源顯著減少。
GraalVM 并不局限于理論創(chuàng)新領(lǐng)域;它受到 Spring Boot、Micronaut、Helidon 和 Quarkus 等主要微服務(wù)框架的支持。此外,Oracle Cloud Infrastructure、Amazon Web Services、Google Cloud Platform 和 Microsoft Azure 等領(lǐng)先的云平臺完全支持 GraalVM 集成。
通過利用配置文件引導(dǎo)的優(yōu)化和先進的 G1(垃圾優(yōu)先)垃圾收集器,GraalVM 使我們的應(yīng)用程序具有更低的延遲。事實上,它提供的性能指標與在 Java 虛擬機 (JVM) 上運行的應(yīng)用程序的性能指標相當或更強。這種速度、效率和安全性的卓越結(jié)合使 GraalVM 成為現(xiàn)代 Java 開發(fā)的改變游戲規(guī)則的選擇。
過去,有很多使用 GraalVM 對 Java 應(yīng)用程序進行基準測試的請求,期望 GraalVM 能夠超越傳統(tǒng)的 Java 虛擬機 (JVM)。
在本篇文章中,我們將對各種 Java 應(yīng)用程序的性能進行比較分析,評估它們在 JVM 和 GraalVM 環(huán)境中的執(zhí)行情況。
我們將通過在 JVM(Java 虛擬機)和 GraalVM 上執(zhí)行基本的“hello world”應(yīng)用程序進行比較分析。通過這個比較,我們旨在探索 GraalVM 相對于傳統(tǒng) JVM 的優(yōu)越性能。
測試設(shè)置
所有測試均在具有 16G RAM 的 MacBook M1 上執(zhí)行。軟件版本有:
- JDK 21
- Graalvm JDK 21
- SpringBoot 3.1.4
應(yīng)用程序代碼是一個包含單個路由的簡單文件:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/")
public String handleRequest() {
return "Hello World!";
}
}
為了構(gòu)建原生鏡像,我們使用了 MVN 的原生插件:
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.example.demo.DemoApplication</mainClass>
<layout>JAR</layout>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.9.27</version>
<extensions>true</extensions>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
<configuration>
<!-- ... -->
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
原生二進制大小約為76M:
76M /Users/mayankc/Work/source/perfComparisons/java/springboot/target/demo
結(jié)果
每個包含500萬個請求的測試分別針對50、100和300個并發(fā)連接執(zhí)行。對于負載測試,我們使用了 Bombardier 測試工具。
以下是表格形式的測試結(jié)果:
統(tǒng)計
為了更直觀的展示測試結(jié)果,我們使用以下公式從結(jié)果中生成記分卡。對于每一個測量,結(jié)果獲勝的按照領(lǐng)先度得分:
- <5%,不給分
- 5%到20%之間,獲勝者得1分
- 在20%到50%之間,獲勝者得2分
- >50%,獲勝者得3分
以下是評分結(jié)果:
結(jié)論
選擇一個簡單的 hello world 案例可能不是真正釋放 GraalVM 或本機代碼編譯潛力的最合適場景。本地運行的相同 SpringBoot 應(yīng)用程序的性能并沒有明顯優(yōu)于其 JVM 對應(yīng)項。GraalVM 唯一顯著的優(yōu)勢在于其對內(nèi)存的高效利用。
本文僅從性能方面對 GraalVM 和 傳統(tǒng) JVM 做了比較,參考以上測試結(jié)果,如果我們想要優(yōu)化程序啟動速度和對內(nèi)存的利用率方面,GraalVM 會是更好的選擇,至于其他性能指標,優(yōu)勢并不明顯!
隨著 GraalVM 在國內(nèi)的推廣和應(yīng)用越來越廣泛,相信它將會在未來的軟件開發(fā)領(lǐng)域發(fā)揮越來越重要的作用,我們期待它之后的表現(xiàn)!