终于,Spring 对 JVM 动手了(spring jar)

createh51个月前 (02-01)技术教程20



Spring 在今年 3 月份推出了 Spring Native Beta 版本,我本来还想着等正式发布了再研究下,不用等了,现在我们就来尝尝鲜。

https://spring.io/blog/2021/03/11/announcing-spring-native-beta

Spring Native 简介

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

Spring Native 特点

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

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

3、即时的峰值性能;

4、更少的内存消耗;

Spring Native 缺点

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

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

2、更少的运行时优化;

3、很多 Java 功能受限;

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

Spring Native 应用场景

1、Spring Cloud 无服务器化(Serverless);

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

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

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

Spring Native 和 JVM 的区别

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

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

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

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

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

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

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

GraalVM 简介

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

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



GraalVM 有以下几个特性。

  • 更加高效快速的运行代码
  • 能与大多数编程语言直接交互
  • 使用 Graal SDK 嵌入多语言
  • 创建预编译的原生镜像
  • 提供一系列工具来监视、调试和配置所有代码

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

重点来看原生镜像功能:

$ javac HelloWorld.java
$ time java HelloWorld
user 0.070s
$ native-image HelloWorld
$ time ./helloworld
user 0.005s

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

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

开始尝鲜

构建 Spring Native 应用的两种方式:

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

2、使用 GraalVM native image Maven 插件来生成一个包含原生可执行文件;

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

1、环境要求

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

  • Linux 需要配置非 root 用户可运行
  • Mac 需要配置最大内存为 8G 或以上

因为我本地已经装好了,这里不再演示了,不会的点击这里阅读参考一下,或者关注:Java技术栈,在历史文章中搜索阅读。

2、添加依赖

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



Spring Boot:


    org.springframework.boot
    spring-boot-starter-parent
    2.4.5
    

Spring Native:


    
        org.springframework.experimental
        spring-native
        ${spring-native.version}
    

注意依赖版本:

Spring Native 最新版本为:0.9.2,只支持 Spring Boot 2.4.5

3、添加 Spring AOT 插件

添加 Spring AOT 插件:


    
        
            org.springframework.experimental
            spring-aot-maven-plugin
            0.9.2
            
                
                    test-generate
                    
                        test-generate
                    
                
                
                    generate
                    
                        generate
                    
                
            
        
    

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

4、开启原生镜像支持

在 Spring Boot Maven 插件中增加以下配置:


    org.springframework.boot
    spring-boot-maven-plugin
    
        
            paketobuildpacks/builder:tiny
            
                true
            
        
    

5、添加 Maven 仓库支持

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


    
        spring-release
        Spring release
        https://repo.spring.io/release
    



    
        spring-release
        Spring release
        https://repo.spring.io/release
    

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


    nexus-aliyun
    *,!spring-release
    Nexus aliyun
    http://maven.aliyun.com/nexus/content/groups/public

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

6、添加测试接口

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

/**
 * 来源:Java技术栈
 */
@SpringBootApplication
@RestController
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }


    @RequestMapping("/native/hi")
    @ResponseBody
    public String hiNative() {
        return "hi native application...";
    }


}

本文所有代码已上传至:
https://github.com/javastacks/spring-boot-best-practice

7、构建原生应用

Maven 插件构建命令:

mvn spring-boot:build-image

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

在 IDEA 插件中运行:



配置好后开始构建:



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



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

8、运行原生应用

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

docker run –rm -p 8080:8080

当然也可以在项目中编写 docker-compose.yml 文件的方式,这里不再演示,感兴趣的可以关注Java技术栈,在历史文章中搜索阅读 Docker 系列文章。

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

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



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

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

http://localhost:8080/native/hi



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

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



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

java -jar spring-boot-native-1.0.jar



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

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

我们再来比对下包的大小

查看刚生成的 Docker 镜像:

docker image ls



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



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

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

总结

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

本文所有演示代码已上传至:

https://github.com/javastacks/spring-boot-best-practice

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

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

https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/#getting-started-native-image

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

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

另外,目前 Spring Native 还处于 Beta 测试版本,现阶段肯定还会存在很多问题,未来可能也还会有变更,不过我会继续关注的,后续我也会更新更多 Java 系列最新技术实战文章,Java技术栈第一时间推送。请大家持续关注哦!

本节所有内容都是参考官网最新文档,可谓是做了第一个吃螃蟹的人,觉得我的文章对你用收获的话,动动小手,给个在看、转发,原创不易,栈长需要你的鼓励。

版权申明:本文系 “Java技术栈” 原创,原创实属不易,转载、引用本文内容请注明出处,禁止抄袭、洗稿,请自重,尊重大家的劳动成果和知识产权,抄袭必究。

相关文章

一章带你了解Java虚拟机——JVM(揭秘java虚拟机:jvm设计原理与实现)

1、JVM 的体系结构"堆"中存在垃圾而"栈"中不存在垃圾的原因:堆(Heap)用途:堆主要用于存储对象实例和数组。在Java中,几乎所有通过new关键字创建的对象都会存储在堆内存中。内存分配与释放:...

阿里P9级架构师马士兵花四小时让你搞懂JVM底层原理(附JVMpdf)

JVM面试难题当面试官向你提问JVM的实战问题时,你是否感觉到无所适从?想解决生产环境中的GC问题,你是否感觉到狗咬刺猬,无处下嘴?面对JVM的海量参数,你是否感觉到迷茫无助?看过很多的JVM文章,但...

JVM(Java虚拟机)从0到1全部合集,强烈建议收藏

JVM(Java虚拟机)现在已经属于Java面试的重灾区,大厂面试现在基本属于必考内容,建议大家需要重点掌握。为了方便大家更好的掌握好JVM(Java虚拟机),也为了让知识更系统化,这里我单独把JVM...

Java 动态调试技术原理及实践(java的动态性指的是什么)

调试是发现和减少计算机程序或电子仪器设备中程序错误的一个过程。最常用的断点调试技术会在断点位置停顿,导致应用停止响应。本文将介绍一种Java动态调试技术,希望能对大家有帮助。同时也欢迎读者朋友们一起交...

JVM简介—1.Java内存区域(jvm内存分布包括哪些部分)

大纲1.运行时数据区的介绍2.运行时数据区各区域的作用3.各个版本内存区域的变化4.直接内存的使用和作用5.站在线程的角度看Java内存区域6.深入分析堆和栈的区别7.方法的出入栈和栈上分配、逃逸分析...

Java面试篇基础部分-JVM详细介绍(java面试必备 基础知识篇)

JVM的运行机制JVM(Java Virtual Machine)是用于运行Java字节码的虚拟计算机,其中包括一套字节码的指令集、程序寄存器、虚拟机栈、虚拟机堆、本地方法区、垃圾回收器。JVM运行在...