最终,JVM还是逃不出spring的魔掌

SpringNative简介

我们都知道,传统的Spring应用程序都是必须依赖于Java虚拟机(JVM)运行的,SpringNative的诞生就是无需JVM,它提供了另外一种运行和部署Spring应用的方式(目前只支持Java和Kotlin),通过GraalVM将Spring应用程序编译成原生镜像。

SpringNative特点

1、无需JVM环境,SpringNative应用程序可以作为一个可执行文件独立部署;

2、应用即时启动,一般情况下应用启动时间ms;

3、即时的峰值性能;

4、更少的内存消耗;

SpringNative缺点

SpringNative应用启动那么快也是有代价的,和JVM应用相比:

1、构建更笨重、构建时间更长;

2、更少的运行时优化;

3、很多Java功能受限;

4、很多特性还很不成熟;

SpringNative应用场景

1、SpringCloud无服务器化(Serverless);

2、以更廉价持久的方式运行Spring微服务;

3、非常适合Kubernetes平台,如:VMwareTanzu;

4、为Spring应用创建更佳的容器镜像;

SpringNative和JVM的区别

1、SpringNative构建时会进行应用程序静态分析;

2、SpringNative构建时会移除未被使用的组件;

3、SpringNative反射、资源、动态代理需要配置化;

4、SpringNative构建时的classpath是固定不变的;

5、SpringNative没有类延迟加载,可执行文件包含所有内容都在启动时加载到内存;

6、SpringNative构建时会运行一些代码;

7、SpringNative对于Java应用程序还存在一些局限性;

GraalVM简介

SpringNative的核心就是Oracle的黑科技:GraalVM。

GraalVM是一个由Oracle开发的全栈通用虚拟机,拥有高性能、跨语言交互等逆天特性,不仅支持了Java、Scala、Groovy、Kotlin等基于JVM的语言,以及C、C++等基于LLVM的语言,还支持其他像JavaScript、Ruby、Python和R语言等,可提高多种语言的运行速度和吞吐量。

GraalVM有以下几个特性。

更加高效快速的运行代码

能与大多数编程语言直接交互

使用GraalSDK嵌入多语言

创建预编译的原生镜像

提供一系列工具来监视、调试和配置所有代码

具体就不介绍了,阅读我之前分享的这篇文章:Oracle发布了一个全栈虚拟机GraalVM

重点来看原生镜像功能:

1.$javacHelloWorld.java

2.$timejavaHelloWorld

3.user0.s

4.$native-imageHelloWorld

5.$time./helloworld

6.user0.s

GraalVM可以预编译成原生镜像,从而极大提速了启动时间,并能减少JVM应用的内存占用。现在你知道为什么SpringNative启动那么快的原因了!

SpringNative正是通过GraalVM提供了对传统Spring应用程序的轻量级运行方式,在不用修改任何传统应用程序代码的情况下,通过集成SpringNative项目就能轻松实现。

开始尝鲜

构建SpringNative应用的两种方式:

1、使用SpringBootBuildpacks来生成一个包含原生可执行文件的轻量级容器;

2、使用GraalVMnativeimageMaven插件来生成一个包含原生可执行文件;

本文使用第一种方式进行尝鲜!

1、环境要求

这种方式需要安装Docker环境:

Linux需要配置非root用户可运行

Mac需要配置最大内存为8G或以上

2、添加依赖

SpringNative在start.spring.io上面已经可以开始使用了,在页面上添加一个SpringNative依赖进去就好,如下所示:

SpringBoot:

parent

groupIdorg.springframework.boot/groupId

artifactIdspring-boot-starter-parent/artifactId

version2.4.5/version

relativePath/

/parent

SpringNative:

dependencies

dependency

groupIdorg.springframework.experimental/groupId

artifactIdspring-native/artifactId

version${spring-native.version}/version

/dependency

/dependencies

注意依赖版本:

SpringNative最新版本为:0.9.2,只支持SpringBoot2.4.5

3、添加SpringAOT插件

添加SpringAOT插件:

build

plugins

plugin

groupIdorg.springframework.experimental/groupId

artifactIdspring-aot-maven-plugin/artifactId

version0.9.2/version

executions

execution

idtest-generate/id

goals

goaltest-generate/goal

/goals

/execution

idgenerate/id

goalgenerate/goal

/executions

/plugin

/plugins

/build

SpringAOT插件执行所需的提前转换,以提升原生镜像的兼容性。

4、开启原生镜像支持

在SpringBootMaven插件中增加以下配置:

plugin

artifactIdspring-boot-maven-plugin/artifactId

configuration

image

builderpaketobuildpacks/builder:tiny/builder

env

BP_NATIVE_IMAGEtrue/BP_NATIVE_IMAGE

/env

/image

/configuration

/plugin

5、添加Maven仓库支持

SpringNative依赖和插件需要在Spring仓库中下载,需要添加以下配置。

repositories

repository

idspring-release/id

nameSpringrelease/name

/repository

/repositories

pluginRepositories

pluginRepository

/pluginRepository

/pluginRepositories

如果不能正常下载Native依赖和插件,需要检查Maven的settings.xml文件:

mirror

idnexus-aliyun/id

mirrorOf*,!spring-release/mirrorOf

nameNexusaliyun/name

/mirror

把mirrorOf值由*修改为:*,!spring-release

6、添加测试接口

添加一个测试接口,原生应用启动后,方便测试下可行性。

/**

*/

SpringBootApplication

RestController

publicclassApplication{

publicstaticvoidmain(String[]args){

SpringApplication.run(Application.class);

}

RequestMapping(/native/hi)

ResponseBody

publicStringhiNative(){

returnhinativeapplication...;

}

7、构建原生应用

Maven插件构建命令:

mvnspring-boot:build-image

这个会创建一个Linux容器,使用GraalVM原生镜像编译器构建出原生应用程序,容器镜像默认只安装在本地。

在IDEA插件中运行:

配置好后开始构建:

会看到大量这样的错误,不用理会,这个会在未来移除。

最终构建完成,一个简单的SpringBoot应用程序,这个构建却过程花了我4分钟。。

8、运行原生应用

使用平常运行Docker镜像的方式就能运行原生应用:

dockerrun--rm-p:

一般情况下,运行原生应用程序只需要毫秒以下,而运行基于JVM的应用程序大概需要15秒左右。

事实是否如此呢,一起来看看!

我天,82毫秒就启动了,启动确实快

再来访问我们之前写的接口:

输出正常,原生应用验证完成。

另外,在target目录中也生成了可执行的jar包:

然后我们用传统JVM环境来运行下:

java-jarspring-boot-native-1.0.jar

启动时间:1.秒,虽然看起来差距不大,但原生应用启动时间(0.秒)也比JVM快了23倍,在不同的代码量面前可能会有较大差距的体现。

当然这只是我测试的参考时间,但可以说明的原生应用运行确实要比JVM快不少!

我们再来比对下包的大小

查看刚生成的Docker镜像:

dockerimagels

查看基于JVM的可执行jar包:

Docker镜像大小:80.7M,而基于JVM运行的可执行jar包却只有不到20M。

这是因为原生镜像不仅包含了应用程序中所使用到的来自JDK、Spring中的必须项,还包含了一个最小化的OS系统层,所以肯定是要比之前的要大不少。

总结

本文介绍了SpringNative的特点,及演示了基于Docker镜像的原生应用。

感兴趣的都可以Star下该仓库,包含了之前写的SpringBoot教程及示例源码。

当然除了基于Docker镜像,还可以使用原生镜像Maven插件的方式,那种方式不需要Docker,但需要安装原生镜像编译器GraalVM,道理是一样的,这里就不再演示了

如果有使用Docker,那第一种肯定是更好的方式,所有的依赖都打包到一个镜像中了,避免了环境污染。

最后总结一下就是,SpringNative可以无需JVM运行,构建慢、启动快、内存占用少、运行优化少,另外还有很多Java特性受限,比如:反射、动态代理等都需要通过提前配置化,因为Java是一种动态链接的语言,原生应用都要提前编译,这个像反射、动态代理这种特性就会受限。




转载请注明:http://www.aierlanlan.com/rzfs/1667.html