以GraalVM原生镜像的方式运行Spring Boot应用程序

Posted java构架师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了以GraalVM原生镜像的方式运行Spring Boot应用程序相关的知识,希望对你有一定的参考价值。

Spring Boot &GraalVM–系列共有3个部分:

  • 第1部分:以GraalVM原生镜像运行Spring Boot应用程序
  • 第2部分:使用Docker&Heroku容器运行Spring BootGraalVM原生镜像
  • 第3部分:使用原生镜像maven插件简化Spring Boot GraalVM原生镜像构建

Spring Boot变GraalVM

在我去年的一个项目中,我遇到了一个使用Spring Boot的情况。这个想法是在Kubernetes上运行所有的微服务,而Java(更准确地说:Spring Boot)被称为太慢太胖了。那时候我真的不得不吞下这片药片,感觉不太好。

这个话题我已经琢磨了很久了!在过去的一年里,随着我越来越多地将注意力转移到DevOps的话题上,我没有时间真正做点什么。但我从未真正离开过Spring的世界。随着2020年的开始,我准备回来看看这个领域是否有新的发展。

而且还有!2019年Spring一号站台,安迪·克莱门特和Sébastien Deleuze就如何将Spring Boot应用程序作为GraalVM本机映像运行进行了一次精彩的演讲。两者还推动了关于GitHub上GraalVM支持的Spring实验项目,在那里您可以密切关注每一步的进展。我想亲爱的斯塔布克斯曼真的推了安迪和Sébastien将发布他们在SpringMilestonesMaven存储库上的工作,这样他就可以写关于SpringTips的介绍(但我相信他会在Twitter上澄清)。但要明确的是,所有想马上开始使用Spring&GraalVM的人:

随着SpringFramework计划于2020年秋季发布的5.3版本,可以预期对SpringBoot提供稳定的GraalVM本机映像支持。

但这不应妨碍我们得到我们的手上这个新的有前途的功能!我真的努力为Spring扫清障碍, Java对于Kubernetes来说太慢太胖了

GraalVM原生镜像和Spring Boot

最近有很多关于GraalVM的传言。codecentric博客还提供了一些阅读内容:Enno Lohmann对Quarkus.io的介绍,或者Timo Kockert对GraalVM的介绍(抱歉,仅限德语)。所以我不会在这里过多地挖掘它的秘密。但是由于GraalVM是许多项目的保护伞,我们需要在这里关注一个特殊的子项目: GraalVM Native Image 。由于我们希望减少Spring Boot应用程序的启动时间和内存占用,所以我们将关注这个项目。

GraalVM原生镜像(或本机映像)的配置主要有两种方式:通过JSON文件进行静态配置或通过动态配置。静态配置文件可以手工制作或在Graal原生映像代理的帮助下生成。动态配置能够处理更复杂的情况。这里可以实现一个特殊的通用特性接口。在GraalVM本机映像构建过程中,实现此接口的类将被回调。

通过将传统上在运行时发生的动态魔法转移到本机映像的编译阶段,可以极大地减少Java应用程序的启动时间和内存占用。当我们考虑在普通Java应用程序中使用一点反射时,这已经是一项很重要的工作,当我们考虑Spring时,这将变得更加困难。它的银弹是它最大的缺点,同时当谈到原生映像生成。尽管类路径扫描和“神奇的”自动配置使我们作为开发人员的生活变得更加轻松,但是 GraalVM原生映像构建过程需要处理它

但是Spring团队真的接受了这个大挑战!安迪·克莱门特和Sébastiendeleuze已经作为一个实验项目为Spring提供了一个Graal@AutomaticFeature的实现。现在已经有大量的示例Spring项目使用这个特性来创建GraalVM原生图像。我发现这真是太棒了,真想仔细看看!

用SDKMAN安装GraalVM

别说了!让我们开始吧。正如有倾向的读者已经知道的那样,我总是努力写100%易懂的博客文章。希望这里的这个也不例外,因此您可以在GitHub上找到一个示例项目: https://github.com/jonashackt/spring-boot-graalvm

为了使用GraalVM,我们需要做的第一件事就是安装它。多亏了我的同事克里斯托夫·达尔斯基,我最近才真正喜欢上了SDKMAN。您可以使用它管理jdk和Java工具,比如Maven或GraalVM。为了使用SDKMAN,我们需要在本地安装它:

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

如果SDKMAN已经成功安装,那么 sdk list java 命令应该显示SDKMAN能够安装的所有可能的jdk:

$ sdk list java
 
================================================================================
Available Java Versions
================================================================================
 Vendor        | Use | Version      | Dist    | Status     | Identifier
--------------------------------------------------------------------------------
 AdoptOpenJDK  |     | 14.0.0.j9    | adpt    |            | 14.0.0.j9-adpt
               |     | 14.0.0.hs    | adpt    |            | 14.0.0.hs-adpt
               |     | 13.0.2.j9    | adpt    |            | 13.0.2.j9-adpt
... 
 GraalVM       |     | 20.1.0.r11   | grl     |            | 20.1.0.r11-grl
               |     | 20.1.0.r8    | grl     |            | 20.1.0.r8-grl
               |     | 19.3.1.r11   | grl     |            | 19.3.1.r11-grl
...

列表本身要长得多,您可以看到这种方法的奇妙的简单性: 永远不要再弄乱JDK安装! 现在要安装基于JDK11的GraalVM,只需运行:

sdk install java 20.1.0.r11-grl

SDKMAN现在为我们安装GraalVM。要正确配置PATH变量,可能需要重新启动控制台。如果一切顺利,您应该看到 java-version 输出如下:

$ java -version
openjdk version "11.0.7" 2020-04-14
OpenJDK Runtime Environment GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02)
OpenJDK 64-Bit Server VM GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02, mixed mode, sharing)

安装GraalVM原生镜像(本机映像)

如前所述,我们需要GraalVM的子项目Native Image来编译Spring Boot应用程序。因此,GraalVM附带了专用工具 gu– GraalVM更新程序。要列出当前安装的所有GraalVM项目,请运行:

$ gu list
ComponentId              Version             Component name      Origin
--------------------------------------------------------------------------------
graalvm                  20.1.0              GraalVM Core

要安装GraalVM本机映像,只需运行:

gu install native-image

之后,本机映像命令应该可以为我们工作,并且可以进行编译工作:

$ native-image --version
GraalVM Version 20.1.0 (Java Version 11.0.7)

创建一个简单的WebFlux REST-Spring Boot应用程序

为了从Spring Boot应用程序创建GraalVM本机映像,我们至少需要一个。最简单的方法就是现在就创建。所以正如著名的starbuxman所建议的,我们需要从Start-Dot-Spring-Dot-IO开始!

【图片】

在那里,我们应该选择至少2.3.0版本的SpringBoot版本。对Spring的GraalVM本地映像支持还处于初级阶段,而且每天都在进步。所以文件上说:

[选择上面的一个版本]SpringBoot2.3.0.M1(您可能可以使用Boot2.2.X,但不能使用2.1或更早版本)

为了构建一个restfulspring启动应用程序,我们需要在这里选择Spring Reactive Web依赖。下载骨架之后,我们继续创建一个简单的服务。在Spring的Reactive方式中,我们首先需要一个类似 HelloHandler.java 的处理程序:

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
 
@Component
public class HelloHandler {
 
    protected static String RESPONSE_TEXT= "Hello Reactive People!";
 
    public Mono<ServerResponse> hello(ServerRequest serverRequest) {
        return ServerResponse
                        .ok()
                        .contentType(MediaType.TEXT_PLAIN)
                        .body(BodyInserters.fromValue(RESPONSE_TEXT));
    }
}

我们还需要一个路由器将HTTP请求路由到我们的处理程序。因此,让我们创建一个 HelloRouter.java

import org.springframework.context.annotation.Bean;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.*;
 
@Component
public class HelloRouter {
 
    @Bean
    public RouterFunction<ServerResponse> route(HelloHandler helloHandler) {
        return RouterFunctions.route(
                RequestPredicates.GET("/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
                serverRequest -> helloHandler.hello(serverRequest)
        );
    }
}

现在我们已经准备好了创建一个测试用例 HelloRouterTest.java ——当然,使用非阻塞 org.springframework.web.reactive.function.client.WebClient

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
 
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class HelloRouterTest {
 
	@Test void
	should_call_reactive_rest_resource(@Autowired WebTestClient webTestClient) {
		webTestClient.get().uri("/hello")
			.accept(MediaType.TEXT_PLAIN)
			.exchange()
			.expectBody(String.class).isEqualTo(HelloHandler.RESPONSE_TEXT);
	}
}

最后,我们在Maven和命令 mvn clean package 的帮助下构建了我们的应用程序。然后我们应该能够像往常一样使用 java -jar 运行它并在上访问它 localhost:8080/hello :

java -jar target/spring-boot-graal-0.0.1-SNAPSHOT.jar

准备将Spring Boot设置为对Graal本机映像友好

现在,为了能够以本机方式编译我们的Spring Boot应用程序,在执行本机映像命令之前,需要做一些准备:

\\1. 将注释类路径扫描从运行时重新定位到构建时

\\2. 禁用GCLIB代理的使用

\\3. 检测自动配置

\\4. 获得Spring Graal @AutomaticFeature

\\5. 在pom.xml中设置 start class 元素

\\6. 为本机映像命令准备配置变量

\\7. 构建应用程序、扩展fat JAR和配置类路径

\\8. 制作本机映像命令

1. 将注释类路径扫描从运行时重新定位到构建时

我们需要处理的第一件事是类路径扫描,因为这在运行时已经不可能了。在整个GraalVM buzz启动之前,就已经有了 spring-context-indexer, ,它是一个注释处理器,将注释扫描从运行时推到构建时:

虽然类路径扫描速度非常快,但通过在编译时创建候选对象的静态列表,可以提高大型应用程序的启动性能。在此模式下,作为组件扫描目标的所有模块都必须使用此机制。

在我们的应用程序中使用 spring-context-indexer, 将很容易。只需通过Maven导入即可:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

这将生成一个 META-INF/spring.components 文件,其中包含通常通过类路径扫描收集的所有spring组件、实体等的列表。

但是我们不必使用 spring-context-indexer ,因为Graal @AutomaticFeature for spring 会自动为我们执行此操作!此外,该特性将追踪导入的带注释类,如 @Import 。它“知道”哪些注释在运行时会导致反射需求,哪些使用GraalVM的注释需要在构建时注册。而且,由于 application.properties 等资源文件也需要在构建时注册,因此该功能也涵盖了这些文件(请记住:编译过程的结果将仅是本机可执行文件)。

2. 禁用GCLIB代理的使用

GraalVM不支持使用GCLIB代理。从springboot2.2开始,GCLIB代理就不再是必需的了。因此,它引入了新的 proxyBeanMethods 选项来避免GCLIB处理。这个示例也在示例项目的 SpringBootHelloApplication.java 中使用:

@SpringBootApplication(proxyBeanMethods = false)
public class SpringBootHelloApplication {
    ...
}

与GCLIB代理不同,GraalVM支持使用JDK代理。它们只需要在构建时注册。这也由Spring-Graal @automatic 特性处理。

3. 检测自动配置

springboot附带了很多自动配置项目,只有在类路径上找到特定的类时,这些项目才会启动。因为这是在运行时完成的,所以它不适用于GraalVM。但是Spring-Graal @automatic 特性也解决了这个问题。它只分析 META-INF/spring.factories 文件,其中通常列出自动配置类。在社区驱动的Spring Boot Starter cxf Spring Boot Starter中可以找到这样一个文件的示例。因此springgraal @AutomaticFeature 再次将工作从运行时拉到构建时,从而消除了运行时自动配置的需要。

4. 获得Spring Graal@AutomaticFeature

正如您已经猜到的:为了将我们的Spring Boot应用程序编译为本机映像,我们需要有最新的 Spring Graal@AutomaticFeature 。当我在2020年3月开始与GraalVM和Spring合作时,没有Maven依赖,因为这个项目处于非常早期的开发阶段。因此,我最初编写了一个脚本 get-spring-feature.sh ,它克隆并构建了该项目以供本地使用。

但是Spring的人走得很快!由于Starbuxman在4月份发布了一篇spring.io帖子,我认为他得到了Andy Clement和Sébastien Deleuze向他发布了一个Maven依赖项,可以在repo.spring.io/milestone上找到

我们开始吧!现在我们不需要手动下载和编译 @AutomaticFeature ,只需在pom.xml中添加一个依赖项即可:

<dependencies>
		<dependency>
			<groupId>org.springframework.experimental</groupId>
			<artifactId>spring-graal-native</artifactId>
			<version>0.6.1.RELEASE</version>
		</dependency>
        ...
        <dependencies>
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
	</pluginRepositories>

另外,请确保Spring Milestones repository定义已经就绪,因为Maven Central现在没有该库!

5. 在pom.xml中设置start-class元素

为了能够执行本机映像编译过程,我们需要提供带有Spring Boot主类全名的命令。

首先,我为 compile.sh 脚本提供了一个参数,稍后我们将查看该参数。但是,由于本机image maven插件也依赖于此设置,我发现在应用程序的pom.xml中提供此类的名称是可以的:

<properties>
		...
		<start-class>io.jonashackt.springbootgraal.SpringBootHelloApplication</start-class>
	</properties>

我们只需要在pom.xml中设置这个类一次,这也很好。我们不需要再麻烦这个参数,因为我们可以在下面的步骤中自动依赖它。

6. 为本机映像命令准备配置变量

我敢肯定,当Spring在2020年底正式发布Graal完全支持时,这里所描述的步骤将不再是必要的,Starbuxman的Spring.io文章也指出了让Maven插件在适当的地方完成繁重工作的方向。但是现在在开发的早期阶段,我发现更深入地了解如何执行 native-image 命令非常有帮助。这对我来说是有回报的——尤其是在这篇文章的后期和下面的博文中。

在spring-graal-native-samples项目中有很多使用编译脚本的好例子。所以让我们试着从中得出我们自己的结论。示例项目中也提供了完整的脚本:

#!/usr/bin/env bash
 
echo "[-->] Detect artifactId from pom.xml"
ARTIFACT=$(mvn -q \\
-Dexec.executable=echo \\
-Dexec.args='${project.artifactId}' \\
--non-recursive \\
exec:exec);
echo "artifactId is '$ARTIFACT'"
 
echo "[-->] Detect artifact version from pom.xml"
VERSION=$(mvn -q \\
  -Dexec.executable=echo \\
  -Dexec.args='${project.version}' \\
  --non-recursive \\
  exec:exec);
echo "artifact version is $VERSION"
 
echo "[-->] Detect Spring Boot Main class ('start-class') from pom.xml"
MAINCLASS=$(mvn -q \\
-Dexec.executable=echo \\
-Dexec.args='${start-class}' \\
--non-recursive \\
exec:exec);
echo "Spring Boot Main class ('start-class') is 'MAINCLASS'"

脚本的第一部分专门定义GraalVM本机映像编译所需的变量。在Maven exec插件的帮助下,可以简单地从pom.xml派生变量 ARTIFACTVERSIONMAINCLASS

7. 构建应用程序、扩展fat JAR和配置类路径

compile.sh 脚本的下一节中,我们清理( aka remove )目标目录,并通过众所周知的 mvn package 命令构建Spring Boot应用程序:

echo "[-->] Cleaning target directory & creating new one"
rm -rf target
mkdir -p target/native-image
 
echo "[-->] Build Spring Boot App with mvn package"
mvn -DskipTests package

构建之后,需要扩展Spring Boot fat JAR,并将类路径设置为结果的内容。另外, Spring Graal @AutomaticFeature 也需要在类路径上可用。因此,我们需要 compile.sh 脚本中 spring-graal-native-0.6.1.RELEASE.jar 文件的正确路径:

echo "[-->] Expanding the Spring Boot fat jar"
JAR="$ARTIFACT-$VERSION.jar"
cd target/native-image
jar -xvf ../$JAR >/dev/null 2>&1
cp -R META-INF BOOT-INF/classes
 
echo "[-->] Set the classpath to the contents of the fat jar (where the libs contain the Spring Graal AutomaticFeature)"
LIBPATH=`find BOOT-INF/lib | tr '\\n' ':'`
CP=BOOT-INF/classes:$LIBPATH

8. 制作本机映像命令

现在终于可以通过许多适当的配置选项触发GraalVM本机映像编译了。如果您需要关于如何配置适合您的SpringBoot应用程序的本机映像命令的灵感,我建议您研究spring-graal-native-samples项目。这些参数现在需要适应不同类型的Spring应用程序,并且看起来非常不同,这取决于您是使用基于Tomcat的应用程序(包括 springdatarest )还是使用基于Netty的反应式应用程序(如本例中所示)。例如,可能需要用 --initializeatbuildtime=class.name.here 定义具体类。您还可以努力完成 native-image 命令抛出的所有异常。有时候除了这个没有别的办法。我也很确定这会随着2020年春季的发布而改变。

我们基于Netty的反应式应用程序的本机映像命令如下所示:

time native-image \\
  --no-server \\
  --no-fallback \\
  -H:+TraceClassInitialization \\
  -H:Name=$ARTIFACT \\
  -H:+ReportExceptionStackTraces \\
  -Dspring.graal.remove-unused-autoconfig=true \\
  -Dspring.graal.remove-yaml-support=true \\
  -cp $CP $MAINCLASS;

目前,大多数Spring Boot应用程序都可以依赖一些参数。尤其应该使用 --no server 标志来确保编译过程产生可再现的结果(GraalVM中现在有一个公开的问题)。另外,当涉及到Spring编译时, Spring Graal@AutomaticFeature 会处理两个默认选项:允许不完整的类路径和在运行时报告不支持的元素。如果我们使用 @AutomaticFeature ,就不需要明确地定义它们。

其他选项需要显式定义: --no fallback 在常规JVM上禁用回退,并强制执行纯本机映像运行时。如果出现问题,参数 -H:+traceClassification-H:+ReportExceptionStackTraces 都将有助于调试。

所有这些参数前面都有 -Dspring.graal 。是Spring Graal特性特定的配置选项。我们在这里使用 -Dspring.graal.remove unused autoconfig=true-Dspring.graal.remove yaml support=true 来实现更快的编译和更小的可执行文件。

最后,需要使用 -H:Name=$ARTIFACT和-cp$cp$MAINCLASS 等其他参数来指定可执行文件的名称和正确的类路径,以便本机映像编译工作。这些文档还提供了本机映像命令可能需要的所有配置参数的列表。

在严重情况下,可能需要将 Spring Graal@Automatic 功能与最初提到的GraalVM代理一起使用。这些文件涵盖了如何进行这种“混合”执行。

运行本机映像编译

现在我们到了我们想去的地方。我们已经准备好了运行本机图像编译的一切。只需执行:

./compile.sh

编译步骤确实需要时间(取决于您的硬件!)。在我的MacBook Pro 2017上,这大约需要3到4分钟。当我们使用 --no server 选项时,您还可以猜测我的机器有多少RAM,因为这个选项还告诉本机映像编译获取大约80%的系统内存。我准备了一个小的记录,这样您就可以看到编译过程是如何工作的:

如果控制台显示以下内容:

[spring-boot-graal:93927]   (typeflow):  74,606.04 ms, 12.76 GB
[spring-boot-graal:93927]    (objects):  58,480.01 ms, 12.76 GB
[spring-boot-graal:93927]   (features):   8,413.90 ms, 12.76 GB
[spring-boot-graal:93927]     analysis: 147,776.93 ms, 12.76 GB
[spring-boot-graal:93927]     (clinit):   1,578.42 ms, 12.76 GB
[spring-boot-graal:93927]     universe:   4,909.40 ms, 12.76 GB
[spring-boot-graal:93927]      (parse):   6,885.61 ms, 12.78 GB
[spring-boot-graal:93927]     (inline):   6,594.06 ms, 12.78 GB
[spring-boot-graal:93927]    (compile):  33,040.00 ms, 12.79 GB
[spring-boot-graal:93927]      compile:  50,001.85 ms, 12.79 GB
[spring-boot-graal:93927]        image:   8,963.82 ms, 12.79 GB
[spring-boot-graal:93927]        write:   2,414.18 ms, 12.79 GB
[spring-boot-graal:93927]      [total]: 232,479.88 ms, 12.79 GB
 
real	3m54.635s
user	16m16.765s
sys	1m55.756s

您现在可以启动第一个GraalVM本机应用程序了!。多酷啊?!!您只需运行生成的可执行文件 /target/native-image/spring-graal-vm :

$ ./target/native-image/spring-graal-vm
 
  .   ____          _            __ _ _
 /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
 \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::
 
2020-05-01 10:25:31.200  INFO 42231 --- [           main] i.j.s.SpringBootHelloApplication         : Starting SpringBootHelloApplication on PikeBook.fritz.box with PID 42231 (/Users/jonashecht/dev/spring-boot/spring-boot-graalvm/target/native-image/spring-boot-graal started by jonashecht in /Users/jonashecht/dev/spring-boot/spring-boot-graalvm/target/native-image)
2020-05-01 10:25:31.200  INFO 42231 --- [           main] i.j.s.SpringBootHelloApplication         : No active profile set, falling back to default profiles: default
2020-05-01 10:25:31.241  WARN 42231 --- [           main] io.netty.channel.DefaultChannelId        : Failed to find the current process ID from ''; using a random value: 635087100
2020-05-01 10:25:31.245  INFO 42231 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2020-05-01 10:25:31.245  INFO 42231 --- [           main] i.j.s.SpringBootHelloApplication         : Started SpringBootHelloApplication in 0.078 seconds (JVM running for 0.08)

我们的Spring Boot 应用程序在 0.078秒 内启动!!

比较启动时间和内存占用

好吧,最初的目标是以闪电般的速度运行我们钟爱的Spring Boot应用程序,并消除Java对于云本地部署来说太慢太胖的“争论”。因此,让我们来看看我们可以运行的“普通”Spring Boot应用程序:

$ java -jar target/spring-boot-graal-0.0.1-SNAPSHOT.jar
 
  .   ____          _            __ _ _
 /\\\\ / ___'_ __ _ _(_)_ __  __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
 \\\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.3.0.M4)
 
2020-04-30 15:40:21.187  INFO 40149 --- [           main] i.j.s.SpringBootHelloApplication         : Starting SpringBootHelloApplication v0.0.1-SNAPSHOT on PikeBook.fritz.box with PID 40149 (/Users/jonashecht/dev/spring-boot/spring-boot-graalvm/target/spring-boot-graal-0.0.1-SNAPSHOT.jar started by jonashecht in /Users/jonashecht/dev/spring-boot/spring-boot-graalvm)
2020-04-30 15:40:21.190  INFO 40149 --- [           main] i.j.s.SpringBootHelloApplication         : No active profile set, falling back to default profiles: default
2020-04-30 15:40:22.280  INFO 40149 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2020-04-30 15:40:22.288  INFO 40149 --- [           main] i.j.s.SpringBootHelloApplication         : Started SpringBootHelloApplication in 1.47 seconds (JVM running for 1.924)

标准方式需要大约 1.47秒 启动,它使用了大约 491 MB 的RAM,这为我们概括了一个简单的 top 命令:

PID    COMMAND      %CPU TIME     #TH  #WQ  #POR MEM  PURG CMPR PGRP  PPID STATE    BOOSTS    %CPU_ME %CPU_OTHRS UID  FAULTS  COW  MSGS MSGR SYSBSD SYSM CSW    PAGE IDLE POWE
40862  java         0.1  00:05.46 27   1    112  491M 0B   0B   40862 1592 sleeping *0[1]     0.00000 0.00000    501  136365  1942 5891 2919 52253+ 8577 21848+ 7148 733+ 0.8

现在与之相比,使用本机编译的Spring Boot应用程序,我们已经看到启动时间只有 78毫秒 。此外,我们的应用程序仅消耗 30 MB 的RAM:

PID    COMMAND      %CPU TIME     #TH  #WQ  #POR MEM  PURG CMPR PGRP  PPID STATE    BOOSTS    %CPU_ME %CPU_OTHRS UID  FAULT COW  MSGS MSGR SYSB SYSM CSW  PAGE IDLE POWE INST CYCL
42231  spring-boot- 0.0  00:00.08 7    1    38   30M  0B   0B   42231 1592 sleeping *0[1]     0.00000 0.00000    501  17416 2360 77   20   2186 186  174  27   2    0.0  0    0

因此,对于一个默认的Spring应用程序,我们有大约500mb的内存消耗,一个本机编译的Spring应用程序只有30mb。 这意味着我们可以用一个标准Spring微服务所需的相同数量的RAM来运行15个以上的Spring微服务! 呜呼!更不用说启动时间了。大约1.5秒而不是78毫秒。因此,即使我们的Kubernetes集群也应该能够以闪电般的速度扩展我们的Spring Boot应用程序!

以闪电般的速度启动–Spring Boot和GraalVM

我完全震惊于SpringBoot和本地编译的GraalVM图像之间的结合是多么成功。感谢Spring团队和springgraal@AutomaticFeature项目的出色工作,我们已经能够看到即将到来的内容。到2020年,我将不再接受任何人告诉我Java/Spring对于真正的云本地部署来说太慢太胖了!当然,还有很长的路要走,生产部署应该等到2020年秋季,春季正式发布全面的GraalVM原生映像支持。但没有理由不从今天开始,看看这些伟大的功能。

以上是关于以GraalVM原生镜像的方式运行Spring Boot应用程序的主要内容,如果未能解决你的问题,请参考以下文章

Spring Native

Spring Native

Spring Native

Spring 官宣,干掉原生 JVM!

Spring 官宣,干掉原生 JVM!

Spring - Spring官宣,干掉原生JVM!