前提

之前曾经写过一篇《SpringBoot3.x 原生镜像-Native Image 尝鲜》,当时SpringBoot处于3.0.0-M5版本,功能尚未稳定。这次会基于SpringBoot当前最新的稳定版本3.1.2详细分析Native Image的实践过程。系统或者软件版本清单如下:

组件 版本 备注
macOS Ventura 13.4.1(c) ARM架构
sdkman 5.18.2 JDK和各类SDK包管理工具
Liberica Native Image Kit 23.0.1.r17-nik 可以构建Native ImageJDK
SpringBoot 3.1.2 使用当前(2023-08-20)最新发布版
Maven 3.9.0 -

安装 sdkman

sdkman是一个轻量级、支持多平台的开源开发工具管理器,可以通过它安装任意主流发行版本(例如OpenJDKKonaGraalVM等等)的任意版本的JDK。通过下面的命令可以轻易安装sdkman:

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

可以通过sdk list java查看支持的JDK发行版本:

通过shell命令sdk install java $Identifier就可以安装对应的JDK发行版。例如可以这样安装GraalVM-ce-17:

sdk install java 17.0.8-graalce

通过shell命令sdk uninstall java $Identifier可以卸载对应的JDK发行版。如果安装了多个版本或者多个发行版的JDK,可以通过shell命令sdk default java $Identifier去指定默认使用的JDK版本,例如:

sdk default java 17.0.8-graalce

可以通过shell命令sdk current或者sdk current java查看当前正在使用的SDK或者JDK版本。

安装 Liberica NIK

Liberica Native Image Kitbellsoft出品的旨在创建高性能原生二进制(Native Binaries)基于JVM编写的应用的工具包,简称为Liberica NIKLiberica NIK本质就是把OpenJDK和多种其他工具包一起封装起来的JDK发行版,在Native Image功能应用过程,可以简单把它视为OpenJDK + GraalVM的结合体。可以通过sdk list java查看相应的JDK版本:

这里选择JDK-17的版本进行安装:

sdk install java 23.0.1.r17-nik
# 这里最好把此JDK设置为当前系统的默认JDK,否则后面编译镜像时候会提示找不到GraalVM
sdk default java 23.0.1.r17-nik

安装完成后,通过java -version验证一下:

编写 SpringBoot 应用

基于Maven新建一个SpringBoot应用,这里已经整理好了一份POM文件,实践过程可以直接用,如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.vlts</groupId>
<artifactId>spring-boot-native-image-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.version>3.11.0</maven.compiler.version>
<maven.install.version>3.1.1</maven.install.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.tomcat.experimental</groupId>
<artifactId>tomcat-embed-programmatic</artifactId>
<version>${tomcat.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<groupId>org.apache.maven.plugins</groupId>
<version>${maven.compiler.version}</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>${maven.install.version}</version>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>cn.vlts.NativeImageApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

这里把Maven的所有插件都提升到当前(2023-08-20前后)最新版本,原生镜像打包的关键插件是native-maven-plugin,此插件是跟随spring-boot-starter-parent进行版本管理,这里无须指定插件的版本。另外,tomcat-embed-programmatic是一个实验性依赖,可以降低嵌入式Tomcat的内存使用,在生产中应用时候可以暂不启用此特性。接着编写启动类cn.vlts.NativeImageApplication

@SpringBootApplication
@RestController
public class NativeImageApplication { public static void main(String[] args) {
SpringApplication.run(NativeImageApplication.class, args);
} @RequestMapping(path = "/")
public ResponseEntity<String> index() {
return ResponseEntity.ok("index");
}
}

构建、测试与发布

三个操作的Maven命令分别是:

  • 构建:mvn -Pnative native:compile
  • 测试:mvn -PnativeTest test
  • 发布:mvn -Pnative spring-boot:build-image,注意此命令会打包镜像并且发布到Docker的官方仓库中

虽然 native:compile 命令表面意义是编译,但是实际上它就是构建原生镜像的命令

执行构建流程:

mvn -Pnative native:compile -Dmaven.test.skip=true

构建结果如下:

其中这个不带.jar后缀的就是最终的原生镜像,并且Native Image是不支持跨平台的,它只能在ARM架构的macOS中运行(受限于笔者的编译环境)。可以发现它(见上图中的target/spring-boot-native-image-demo,它是一个二进制执行文件)的体积比executable jar大好几倍。参照SpringBoot的官方文档,经过AOT编译的SpringBoot应用会生成下面的文件:

  • Java源代码
  • 字节码(例如动态代理编译后的产物等)
  • GraalVM识别的提示文件:
    • 资源提示文件(resource-config.json
    • 反射提示文件(reflect-config.json
    • 序列化提示文件(serialization-config.json
    • Java(动态)代理提示文件(proxy-config.json
    • JNI提示文件(jni-config.json

这里的输出非执行包产物基本都在target/spring-aot目录下,其他非Spring或者项目源代码相关的产物输出到graalvm-reachability-metadata目录中。最后可以验证一下产出的Native Image

可以看到启动速度达到惊人的毫秒级别,如果应用在生产中应该可以全天候近乎无损发布。当然,理论上Native Image性能也会大幅度提升,但是限于篇幅这里暂时不进行性能测试。

小结

鉴于SpringBoot3.x的正式版已经推出一段时间,从文档上看,Native Image使用的技术已经相对成熟,可以放心用于生产环境。当然,Native Image目前还存在一些局限性会让一些组件完全无法使用或者部分功能受限(参考Spring Boot with GraalVM),希望这些问题或者局限性有一天能够突破让所有JVM应用迎来一次性能飞跃。

(本文完 c-2-d e-a-20230820)

SpringBoot3.x原生镜像-Native Image实践的更多相关文章

  1. SpringBoot3.x原生镜像-Native Image尝鲜

    前提 Spring团队致力于为Spring应用程序提供原生映像支持已经有一段时间了.在SpringBoo2.x的Spring Native实验项目中酝酿了3年多之后,随着Spring Framewor ...

  2. 8.云原生之Docker容器镜像构建最佳实践浅析

    转载自:https://www.bilibili.com/read/cv15220861/?from=readlist 本章目录 0x02 Docker 镜像构建最佳实践浅析 1.Dockerfile ...

  3. 未来已来:云原生 Cloud Native

    作者:天知,原文链接 前言 自 2013 年容器(虚拟)技术(Docker)成熟后,后端的架构方式进入快速迭代的阶段,出现了很多新兴概念: 微服务 k8s Serverless IaaS:基础设施服务 ...

  4. 制作 Python Docker 镜像的最佳实践

    概述 ️Reference: 制作容器镜像的最佳实践 这篇文章是关于制作 Python Docker 容器镜像的最佳实践.(2022 年 12 月更新) 最佳实践的目的一方面是为了减小镜像体积,提升 ...

  5. 微信原生支付 Native扫码支付( V3.3.7 版本)

    原文:微信原生支付 Native扫码支付( V3.3.7 版本) [尊重别人的劳动成果,转载请注明出处:一缕晨光工作室,www.wispdawn.com] 前言 辛苦研究三天,遇到各种困难,最终还是克 ...

  6. 深度解读阿里巴巴云原生镜像分发系统 Dragonfly

    Dragonfly 是一个由阿里巴巴开源的云原生镜像分发系统,主要解决以 Kubernetes 为核心的分布式应用编排系统的镜像分发难题.随着企业数字化大潮的席卷,行业应用纷纷朝微服务架构演进,并通过 ...

  7. 5大最新云原生镜像构建工具全解析,3个来自Google,你了解几个?

    1云原生大背景下的镜像构建在分享开始,我想先跟大家简单聊一下云原生,可能不会详细展开,而是带领大家了解一下云原生对镜像构建方面的影响.第一,在接触云原生相关的技术时,无论是要解决开发.测试环境的问题, ...

  8. 关于OAuth2.0 Authorization Code + PKCE flow在原生客户端(Native App)下集成的一点思考

    写在前面 前几天看了园友的一篇文章被广泛使用的OAuth2.0的密码模式已经废了,放弃吧 被再次提起: Implicit Flow Password Grant,均已被标记为Legacy,且OAuth ...

  9. 精彩分享 | 欢乐游戏 Istio 云原生服务网格三年实践思考

    作者 吴连火,腾讯游戏专家开发工程师,负责欢乐游戏大规模分布式服务器架构.有十余年微服务架构经验,擅长分布式系统领域,有丰富的高性能高可用实践经验,目前正带领团队完成云原生技术栈的全面转型. 导语 欢 ...

  10. react native 入门实践

    上周末开始接触react native,版本为0.37,边学边看写了个demo,语法使用es6/7和jsx.准备分享一下这个过程.之前没有native开发和react的使用经验,不对之处烦请指出.希望 ...

随机推荐

  1. 2021-03-29:无序数组arr,子数组-1和1的数量一样多,请问最长子数组的长度是多少?

    2021-03-29:无序数组arr,子数组-1和1的数量一样多,请问最长子数组的长度是多少? 福大大 答案2021-03-29: [1, -1, 2, 3, -4, -1, 9]变成[1, -1, ...

  2. 2021-10-19:缺失的区间。给定一个排序的整数数组 nums ,其中元素的范围在 闭区间 [lower, upper] 当中,返回不包含在数组中的缺失区间。力扣163。

    2021-10-19:缺失的区间.给定一个排序的整数数组 nums ,其中元素的范围在 闭区间 [lower, upper] 当中,返回不包含在数组中的缺失区间.力扣163. 福大大 答案2021-1 ...

  3. 时间函数strftime和strptime的差别

    strftime是转换为特定格式输出, strptime是将一个时间字符串解析为时间类型对象. strftime是按照想要的格式,去转换.重点是格式! strptime不管什么格式,只要把特定的时间字 ...

  4. lec-1-Deep Reinforcement Learning, Decision Making, and Control

    What is RL 基于学习的决策的数学形式 从经验中学习决策和控制的方法 Why should we study this now 深度神经网络特征方法 强化学习的提升 计算能力的提升 我们还需要 ...

  5. java开发学习框架

    Java基础 1.1. Java简介与安装 1.2. Java基本语法 1.3. 数据类型与变量 1.4. 运算符与表达式 1.5. 流程控制(分支与循环) 1.6. 数组 面向对象编程 2.1. 类 ...

  6. Linux下程序时间消耗监控与统计

    良好的计时器可帮助程序开发人员确定程序的性能瓶颈,或对不同算法进行性能比较.但要精确测量程序的运行时间并不容易,因为进程切换.中断.共享的多用户.网络流量.高速缓存访问及转移预测等因素都会对程序计时产 ...

  7. 使用numpy计算分子内坐标

    技术背景 当我们打开一个用于表示分子构象的xyz文件或者pdb文件,很容易可以理解这种基于笛卡尔坐标的空间表征方法.但是除了笛卡尔坐标表示方法之外,其实也有很多其他的方法用于粗粒化或者其他目的的表征方 ...

  8. 深入解析React DnD拖拽原理,轻松掌握拖放技巧!

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值.. 本文作者:霁明 一.背景 1.业务背景 业务中会有一些需要实现拖拽 ...

  9. vue中的数据代理

    原理:通过vm对象来代理 Vue 中我们自己定义在data中的数据,首先: 我们自己定义的data中的对象或者属性 都会存储到vm中的_data 中进行数据劫持其次: 通过Object.defineP ...

  10. Rust 声明式宏中的 Metavariables 有哪些

    Metavariables 官方文档确实写得很好,但是缺少一些风味,容易催眠‍ 还是直接看例子更爽一些,通常我们可以从示例代码中之间看出官方文档要表达的意思,而且很多时候我们可以直接在示例代码的基础上 ...