本人资历、水平有限,如有错误请大佬们批评指正,谢谢!

你身边的项目架构云原生吗,还是只是云却不原生?
Java 能够像 C、Go 等语言一样做静态编译吗?如果能,会对现有架构造成什么样的影响?
因 JVM 的短板,相关技术栈对于云原生架构来说不是一个很好的选择,难道 JVM 在云原生架构方面只能被后浪们蚕食吗?

文章目录

前言

基于 JVM 的应用启动比原生应用慢一拍

平时 Java 用得比较多,也会用 Golang 或者 C 等静态编译语言的人,应该多少都会感觉到,即使是最简单的 hello, world,Java 程序的启动速度相比静态编译出的可执行程序会慢上一拍。
如果使用 Java 等语言实现的运行在 JVM 上的 CLI 工具,这种慢一拍的感觉应该会更明显(使用过 RocketMQmqadmin 脚本的同学应该有所体会)。
Spring Framework 全家桶的启动就更不用说了,大项目启动时间可达 分钟 级别……

先来个简单的用户体验实验:

一段 Java 的 hello, world

public class Main {
	public static void main(String[] args) {
		System.out.println("hello, java");
	}
}

一段 Golang 的 hello, world

package main
func main() {
	println("hello, go")
}
➜  ~ go build main.go
➜  ~ repeat 10 time ./main
hello, go
./main  0.00s user 0.00s system 107% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 118% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 110% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 109% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 112% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 119% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 109% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 108% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 106% cpu 0.001 total
hello, go
./main  0.00s user 0.00s system 107% cpu 0.001 total

➜  ~ javac Main.java      
➜  ~ repeat 10 time java Main
hello, java
java Main  0.07s user 0.04s system 119% cpu 0.087 total
hello, java
java Main  0.07s user 0.02s system 115% cpu 0.074 total
hello, java
java Main  0.06s user 0.01s system 114% cpu 0.063 total
hello, java
java Main  0.06s user 0.01s system 146% cpu 0.046 total
hello, java
java Main  0.06s user 0.01s system 147% cpu 0.049 total
hello, java
java Main  0.05s user 0.02s system 142% cpu 0.046 total
hello, java
java Main  0.05s user 0.01s system 143% cpu 0.045 total
hello, java
java Main  0.05s user 0.01s system 142% cpu 0.045 total
hello, java
java Main  0.05s user 0.02s system 144% cpu 0.045 total
hello, java
java Main  0.05s user 0.01s system 142% cpu 0.045 total

注意!以上测试 仅用于比较用户体验 ,不代表 Golang 与 Java 的性能好坏。

Golang 写的 hello, world 基本是 敲下键盘那一刻就已经运行完毕退出了,Java 写的 hello, world 人类可以感受到敲下键盘那一刻命令行的停滞,尤其是冷启动(首次启动 JVM 进程,可能长达数百毫秒。在树莓派这类硬件上,JVM 冷启动可长达数秒)。

运行期间性能不差,启动笨重一点又怎么样?

基于 JVM 的应用确实需要一定的启动时间(尤其是 Spring),启动内存相比原生应用也不低。“速度慢,吃内存” 成了大家黑 Java 的关键点。

不过,在目前常见的架构下,这些问题不会特别突出:

  • 在应用运行期间,因 JVM 的各种优化手段,应用的运行性能会逐渐优化,不会比原生应用差。
  • 在内存方面,JVM 所需内存对于一般服务器也没有什么压力。
  • 启动慢的问题,在有滚动发布机制的环境中,应用多花一点时间进入就绪状态也没什么问题。

这么看来其实 “速度慢,吃内存” 也不是什么问题。

但是, 脱离场景谈技术都是耍流氓

对于一些用户群体比较确定、不会有突发流量,充分评估过资源需求、本身比较稳定的应用,启动时间和内存占用一般不会有什么问题。

但是对于弹性计算、存在突发流量等需要时刻准备水平扩容的场景,JVM 的短板就被放大了。

例如基于事件驱动的函数计算,按需分配资源,不用的时候不占用资源。不预留实例的函数计算,只有事件发生的时候才会分配资源并创建实例。

从以下三个方面谈 JVM 的短板:

  1. 在启动时间方面:
    如果函数实例是后台计算等不涉及用户体验的操作,实例启动慢一点影响不会很大。
    但如果函数提供的服务具有即时性、涉及用户体验、对时间敏感,实例启动时间就会在很大程度上影响着用户体验。原生应用和 JVM 相比,本身就没有启动的过程(本人的理解,从另一个角度说,操作系统的启动就是原生应用的启动)。因应用经过静态编译,业务逻辑的启动一般也不会占用太多时间(除非涉及 I/O 等对于 CPU 来说很慢的操作)。

  2. 在内存占用方面:
    例如使用阿里云函数计算服务,内存需求关系到费用问题。一般虚拟机和原生应用相比在内存占用方面没有优势。一段相同逻辑的代码分别使用虚拟机和原生应用运行,虚拟机内存需求一般不会低于原生应用。

  3. 在运行期间:
    JVM 有很多优化手段,基于 JVM 的应用经过时间的推移或者主动进行预热,可能会越来越快。但是,在弹性计算这类场景中,JVM 可能根本来不及优化,实例就已经被释放了。没有经过优化的 JVM 应用在运行速度上很难和原生应用的机器语言匹敌。

所以,回答标题的问题:

在现在这种新架构、新技术百花齐放的时代,如果 JVM 相关技术栈不能与时俱进,将很容易被后来者蚕食。

GraalVM - Run Programs Faster Anywhere

GraalVM 是 Oracle 实验室的黑科技之一。
和 Java 的 “一次编译,到处运行” 不同,GraalVM 是 “让程序在任何地方运行更快”。

特性:

  • 增加应用程序的吞吐量、减少延迟
  • 将应用程序编译为独立小巧的本地可执行二进制程序
  • 无缝使用多种语言和库

篇幅有限,本文只关注 Native Image。除了 Native Image,GraalVM 还有很多黑科技。

官网:https://www.graalvm.org/
Download
根据官网的下载页面,其实 GraalVM 就是基于 OpenJDK / Oracle JDK 构建的,并不是什么新的虚拟机,本身还是 HotSpot VM,现有的项目可以将虚拟机直接切换 GraalVM。

要说新研发的虚拟机,本人根据资料理解,构建好的原生应用中的 SubstrateVM (SVM) 就是一个新的虚拟机(SVM 系列还包含了 AOT 等工具)。

Native Image,将字节码编译为真正的原生可执行程序

Native Image 可以在封闭环境下将一个基于 JVM 的应用通过 AOT 编译为二进制可执行程序或动态库。

这里的可执行程序并不是内嵌 JRE,而是真正的原生可执行程序。

编译器以字节码作为输入,因此支持各种基于 JVM 的语言。

Quarkus - Supersonic Subatomic Java

称为 “超声波 Java” 的 Quarkus 有什么特性?

CONTAINER FIRST(容器优先)
  1. Quarkus 完全支持 Graal / SubstrateVM:能够直接构建 Native Image 可执行程序
  2. Quarkus 会在构建时期尽量将不需要用到的类等可精简项排除掉,以实现更短的启动时间和更少的内存占用
  3. Quarkus 尽量避免使用反射(Reflection),减少启动时间和内存占用
  4. Quarkus 会尽量将一些启动工作放在 Native Image 构建期间完成,以减少启动时间

官方提供的 Quarkus原生应用、Quarkus JVM、传统云原生技术栈的内存占用、启动时间对比图:

UNIFIES IMPERATIVE AND REACTIVE(统一命令式和响应式编程)

无论是使用命令式还是响应式编程,都有相近的代码风格

命令式:

@Inject
SayService say;

@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
    return say.hello();
}

响应式:

@Inject @Channel("kafka")
Publisher<String> reactiveSay;

@GET
@Produces(MediaType.SERVER_SENT_EVENTS)
public Publisher<String> stream() {
    return reactiveSay;
}
DEVELOPER JOY(开发者友好)
  1. Quarkus 统一了各种不同的技术框架的配置
  2. Quarkus 在开发模式下可以实现保存代码即运行,而且开箱即用,无需额外配置
  3. Quarkus 支持各类构建工具,Maven, Gradle 或者 IDE
  4. Quarkus 将尽量统一开发的方式与风格
BEST OF BREED LIBRARIES AND STANDARDS(基于最好的库和标准)

Quarkus 不会让用户花费大量时间学习新技术,其编程模型建立在各种已有的标准之上,例如:

  • JPA
  • Spring
  • JTA
  • Vert.x
  • CDI
  • JAX-RS
  • Apache Camel

环境准备

本文环境概览

有条件的建议在 Linux 进行,Windows 上进行可能坑会稍多。
Environment

准备 GraalVM

GraalVM 可以在 Oracle 官网下载:https://www.oracle.com/downloads/graalvm-downloads.html?selected_tab=20l
注意版本的选择!

国内网络环境下载速度可能较慢,此处提供本人在用的版本:

GraalVM 的安装使用和普通的 JDK 没有区别,下载后解压并设置环境变量就可以了。

GraalVM 相比普通的 OpenJDK / Oracle JDK 增加了一些命令,例如用于管理 GraalVM 组件的命令 gu

不要忘记安装 Native Image 组件!可以通过 gu 命令安装提前下载好 Native Image 组件的 jar。
Native Image 组件安装文档:https://docs.oracle.com/en/graalvm/enterprise/20/guide/reference/native-image.html

➜  ~ javac -version
javac 11.0.7

➜  ~ java -version 
java version "11.0.7" 2020-04-14 LTS
Java(TM) SE Runtime Environment GraalVM EE 20.1.0 (build 11.0.7+8-LTS-jvmci-20.1-b02)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 20.1.0 (build 11.0.7+8-LTS-jvmci-20.1-b02, mixed mode, sharing)

➜  ~ native-image 
Please specify options for native-image building or use --help for more info.

环境基本准备好了!

使用 Native Image 可能还需要 gcc 等开发工具,各环境所需依赖可以参考 Quarkus 文档:
https://quarkus.io/guides/building-native-image#prerequisites


初尝 GraalVM 的 Native Image

hello, world 的 Native Image

native-image 使用方法

native-image 命令的帮助信息节选:

➜  hello-graalvm $GRAALVM_HOME/bin/native-image --help

GraalVM native-image building tool

This tool can be used to generate an image that contains ahead-of-time compiled Java code.

Usage: native-image [options] class [imagename] [options]
           (to build an image for a class)
   or  native-image [options] -jar jarfile [imagename] [options]
           (to build an image for a jar file)

可以看到,native-image 命令参数接受类的字节码文件或 jar ,我们直接将文章前言部分通过 javac 编译的 Main.class 拿来用。

构建 hello, world

此处写的是 Main 而不是 Main.class,指定生成的可执行程序名为 main_native

➜  hello-graalvm $GRAALVM_HOME/bin/native-image Main main_native
Build on Server(pid: 29364, port: 34701)*
[main_native:29364]    classlist:     987.61 ms,  0.96 GB
[main_native:29364]        (cap):     590.98 ms,  0.96 GB
[main_native:29364]        setup:   2,064.60 ms,  0.96 GB
[main_native:29364]     (clinit):      99.22 ms,  1.21 GB
[main_native:29364]   (typeflow):   3,109.82 ms,  1.21 GB
[main_native:29364]    (objects):   3,299.24 ms,  1.21 GB
[main_native:29364]   (features):     177.67 ms,  1.21 GB
[main_native:29364]     analysis:   6,857.44 ms,  1.21 GB
[main_native:29364]     universe:     199.91 ms,  1.21 GB
[main_native:29364]      (parse):     518.11 ms,  1.21 GB
[main_native:29364]     (inline):   1,130.87 ms,  1.66 GB
[main_native:29364]    (compile):   7,976.54 ms,  3.15 GB
[main_native:29364]      compile:  10,090.57 ms,  3.15 GB
[main_native:29364]        image:     636.62 ms,  3.15 GB
[main_native:29364]        write:     176.02 ms,  3.15 GB
[main_native:29364]      [total]:  21,153.23 ms,  3.15 GB

以上是构建 Native Image 过程中的输出,4列信息分别代表:

  1. 目标 Native Image 名称与 NativeImageBuildServer 的 Pid
  2. 构建步骤
  3. 步骤耗时
  4. NativeImageBuildServer 的堆大小(数值比 top 命令监控到的内存稍小,本人猜测是堆大小)

构建完成后,目录中会有一个 main_native 可执行文件,重复执行 10 次:

➜  hello-graalvm ll ?ain*
-rwxr-xr-x 1 sia sia 1.2M 6月   2 22:30 main
-rw-r--r-- 1 sia sia  413 6月   2 23:18 Main.class
-rw-r--r-- 1 sia sia   51 6月   2 22:21 main.go
-rw-r--r-- 1 sia sia  104 6月   2 23:18 Main.java
-rwxr-xr-x 1 sia sia 8.1M 6月   6 10:53 main_native

➜  hello-graalvm repeat 10 time ./main_native
hello, java
./main_native  0.00s user 0.00s system 94% cpu 0.009 total
hello, java
./main_native  0.00s user 0.01s system 91% cpu 0.008 total
hello, java
./main_native  0.00s user 0.01s system 88% cpu 0.006 total
hello, java
./main_native  0.00s user 0.00s system 94% cpu 0.004 total
hello, java
./main_native  0.00s user 0.00s system 92% cpu 0.003 total
hello, java
./main_native  0.00s user 0.00s system 93% cpu 0.002 total
hello, java
./main_native  0.00s user 0.00s system 91% cpu 0.002 total
hello, java
./main_native  0.00s user 0.00s system 87% cpu 0.002 total
hello, java
./main_native  0.00s user 0.00s system 91% cpu 0.002 total
hello, java
./main_native  0.00s user 0.00s system 93% cpu 0.002 total

对比一下前言中的实验结果,native-image 构建出来的 “hello, world” 可执行程序启动速度明显比通过 JVM 运行快很多,也达到了 ”敲下键盘那一刻已经运行完毕并退出” 的境界。

关于 NativeImageBuildServer 与构建过程资源占用

➜  hello-graalvm jps -mlvV | grep 29364 
29364 com.oracle.svm.hosted.server.NativeImageBuildServer -port=0 -logFile=/home/sia/.native-image/machine-id-ced39042e1ef44249ce6b3f25738a0e8/session-id-58f6/server-id-c54e3f0f73a1cc48d8cb58b05946a06b73cb841f608d5a7f9e62c46e06da98b438a2317d2bc2fcff22466f6a8846ce5cf572ac3413fb36f93e5238d3785da173/server.log -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCIProduct -XX:-UnlockExperimentalVMOptions -XX:ThreadPriorityPolicy=1 -XX:+UseParallelGC -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -Dtruffle.TrustAllTruffleRuntimeProviders=true -Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime -Dgraalvm.ForcePolyglotInvalid=true -Dgraalvm.locatorDisabled=true -Dsubstratevm.IgnoreGraalVersionCheck=true -Djava.lang.invoke.stringConcat=BC_SB --add-exports=jdk.internal.vm.ci/jdk.vm.ci.runtime=ALL-UNNAMED --add-exports=jdk.internal.vm.ci/jdk.vm.ci.code=ALL-UNNAMED --add-exports=jdk.internal.vm.ci/jdk.vm.ci.aarch64=ALL-UNNAMED --add-exports=jdk.internal.vm.ci/jdk.vm.ci.amd64=ALL-UNNAMED --add-exports=jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED --add-exports=jdk.internal.vm.ci/jdk.vm.ci.hotspot=ALL-UNNAMED --add-exports=jdk.internal.vm.ci/jdk.vm.ci.services=ALL-UNNAMED --add-exports=jdk.internal.vm.ci/jdk.vm.ci.common=ALL-UNNAMED --add-exports=jdk.internal.vm.ci/jdk.vm.ci.code.site=ALL-UNNAMED --add-e

通过 jps 命令可以看到,native-image 会启动一个 NativeImageBuildServer 用于构建可执行程序。可执行程序构建完成后,该 Server 仍驻留,下次构建将使用该实例(再次构建同样的可执行程序耗时与内存 [main_native_2:29364] [total]: 14,191.21 ms, 4.55 GB,耗时更少,但内存占用有所增加)

在另一个 Session 中启动的 top 命令,每 1 秒采集一次 graalvm 相关进程的运行状况。

➜  hello-graalvm top -d 1 -n60 -c | grep graalvm
29183 sia       20   0 32.264g 0.083g 0.014g S  27.6  0.1   0:00.29 /usr/local/graalvm-ee-java11-20.1.0/bin/native-i+ 
29364 sia       20   0 48.938g 0.078g 0.041g S  26.7  0.1   0:00.28 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 51.055g 0.546g 0.071g S 576.7  0.9   0:06.22 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 51.021g 0.638g 0.072g S 298.0  1.0   0:09.26 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 51.024g 0.663g 0.073g S 377.7  1.1   0:13.15 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29183 sia       20   0 32.264g 0.083g 0.014g S   1.0  0.1   0:00.30 /usr/local/graalvm-ee-java11-20.1.0/bin/native-i+ 
29364 sia       20   0 51.571g 0.802g 0.074g S 534.0  1.3   0:18.65 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 51.573g 0.856g 0.074g S 720.6  1.4   0:26.00 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 51.576g 0.922g 0.074g S 493.3  1.5   0:31.13 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 51.576g 0.923g 0.074g S 302.9  1.5   0:34.25 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 51.576g 0.988g 0.075g S 281.4  1.6   0:37.12 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 51.576g 1.055g 0.075g S 135.0  1.7   0:38.51 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 52.371g 1.147g 0.075g S 223.3  1.8   0:40.81 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.238g 1.311g 0.075g S 823.5  2.1   0:49.21 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.240g 1.315g 0.075g S 770.5  2.1   0:57.30 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.243g 1.660g 0.075g S 989.3  2.6   1:07.49 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.243g 1.762g 0.075g S 982.1  2.8   1:17.90 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.245g 1.864g 0.075g S  1024  3.0   1:28.75 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.246g 2.331g 0.075g S  1003  3.7   1:39.38 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.248g 2.337g 0.075g S 867.0  3.7   1:48.57 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.248g 2.366g 0.075g S  1030  3.8   1:59.18 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.250g 2.940g 0.075g S 917.3  4.7   2:09.27 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.252g 3.211g 0.075g S 516.5  5.1   2:14.59 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.155g 3.272g 0.075g S 408.7  5.2   2:18.80 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 
29364 sia       20   0 53.155g 3.273g 0.075g S   0.9  5.2   2:18.81 /usr/local/graalvm-ee-java11-20.1.0/bin/java -cp+ 

第 9, 10 列分别是 CPU 和内存利用率。(CPU 利用率是每个逻辑核心利用率的和,理论上 12T 的 CPU 最高利用率为 1200)
本来我以为执行 native-image 就和 go build 差不多,没想到,本次构建的只是一个 hello, world,就已经占用了大量计算资源和近 3.3G 内存,这是一个不低的门槛。(剧透一下,Quarkus 构建可执行程序的占用资源会高很多)

Native Image 使用没有限制吗?肯定有

既然要做静态分析编译,肯定有所牺牲。

文档:https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md

动态类加载、反射、动态代理、JNI、JCA 等特性在部分场景需要配置

以反射为例:
Java 和 Go 都有反射 (Reflection),使用过的人应该清楚,Go 的反射能力相比 Java 的反射能力有所差距(例如 Go 不能直接办到 Java 的 Class.forName())。

如果构建 Native ImageClass.forName() 等方法在一些场景下(例如从外部获取或动态编译代码等,无法在 native-image 构建期间分析的代码)需要提供配置文件指定一些信息。(这块资料较少)

配置相关资料:

已知类的反射可以正常执行

来一个简单的示例,动态加载 java.lang.Integer 并获取 MAX_VALUE 的值:

NativeImageReflection.java

package vip.wuweijie.test.graalvm;

/**
 * @author wuweijie
 */
public class NativeImageReflection {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        System.out.println(Class.forName("java.lang.Integer").getField("MAX_VALUE").get(null));
    }
}

编译为 class 后用 java 直接运行:

➜  classes java vip.wuweijie.test.graalvm.NativeImageReflection 
2147483647

构建可执行程序并运行:

➜  classes $GRAALVM_HOME/bin/native-image vip.wuweijie.test.graalvm.NativeImageReflection reflect
Build on Server(pid: 29364, port: 34701)
[reflect:29364]    classlist:      90.08 ms,  5.13 GB
[reflect:29364]        (cap):     280.83 ms,  5.13 GB
[reflect:29364]        setup:     618.75 ms,  5.13 GB
[reflect:29364]     (clinit):     102.93 ms,  5.13 GB
[reflect:29364]   (typeflow):   2,906.36 ms,  5.13 GB
[reflect:29364]    (objects):   3,788.48 ms,  5.13 GB
[reflect:29364]   (features):     146.51 ms,  5.13 GB
[reflect:29364]     analysis:   7,128.26 ms,  5.13 GB
[reflect:29364]     universe:     185.85 ms,  5.13 GB
[reflect:29364]      (parse):     318.00 ms,  5.13 GB
[reflect:29364]     (inline):     447.50 ms,  5.13 GB
[reflect:29364]    (compile):   4,994.34 ms,  5.20 GB
[reflect:29364]      compile:   6,114.72 ms,  5.20 GB
[reflect:29364]        image:     545.57 ms,  5.20 GB
[reflect:29364]        write:     124.16 ms,  5.20 GB
[reflect:29364]      [total]:  14,853.08 ms,  5.20 GB

➜  classes ./reflect     
2147483647

封闭优化不支持的特性

invokedynamic(部分支持)

通过 javac 生成的 invokedynamic 仍然可以支持,例如 Lambda 和 String 拼接等。

Serialization(序列化,需要通过配置才能支持)

影响范围可能涉及到 java.rmi

Security Manager(安全管理)

建议使用独立进程(separate process)运行。

部分特性在 SVM 的实现会有所差异

Class Initializers

类初始化操作可以在构建 Native Image 时进行,也可以在应用启动时进行。有点高深,此处不展开。

不再调用 finalize() 方法

由于 finalize() 方法从 Java 9 开始废弃,SVM 将不会调用该方法,建议使用弱引用或引用队列代替 finalize() 方法。

SVM 未实现 java.lang.Thread 中的废弃方法

java.lang.Thread 类中,SVM 没有实现已经被废弃的方法,例如 Thread.stop() 等。

Unsafe 相关

有点高深,此处不展开。

尝试构建 Spring Boot 项目的 Native Image

默认参数构建出 Fallback Image

有一个 Spring Boot Eureka Server 的 jar,构建 Native Image:

➜  libs $GRAALVM_HOME/bin/native-image -jar eureka-server-1.0-SNAPSHOT.jar eureka-server
Build on Server(pid: 29364, port: 34701)
[eureka-server:29364]    classlist:     107.58 ms,  5.12 GB
[eureka-server:29364]        (cap):     308.11 ms,  5.12 GB
[eureka-server:29364]        setup:     628.09 ms,  5.12 GB
[eureka-server:29364]     (clinit):     127.87 ms,  5.12 GB
[eureka-server:29364]     analysis:  13,088.76 ms,  5.12 GB
Warning: Aborting stand-alone image build due to unsupported features
Warning: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Build on Server(pid: 29364, port: 34701)
[eureka-server:29364]    classlist:      79.87 ms,  5.12 GB
[eureka-server:29364]        (cap):     277.76 ms,  5.12 GB
[eureka-server:29364]        setup:     556.38 ms,  5.12 GB
[eureka-server:29364]     (clinit):      78.21 ms,  5.12 GB
[eureka-server:29364]   (typeflow):   2,907.80 ms,  5.12 GB
[eureka-server:29364]    (objects):   3,545.23 ms,  5.12 GB
[eureka-server:29364]   (features):      70.02 ms,  5.12 GB
[eureka-server:29364]     analysis:   6,753.20 ms,  5.12 GB
[eureka-server:29364]     universe:     113.26 ms,  5.12 GB
[eureka-server:29364]      (parse):     310.65 ms,  5.12 GB
[eureka-server:29364]     (inline):     430.37 ms,  5.12 GB
[eureka-server:29364]    (compile):   3,658.23 ms,  5.17 GB
[eureka-server:29364]      compile:   4,703.36 ms,  5.17 GB
[eureka-server:29364]        image:     406.92 ms,  5.17 GB
[eureka-server:29364]        write:     150.86 ms,  5.17 GB
[eureka-server:29364]      [total]:  12,805.75 ms,  5.17 GB
Warning: Image 'eureka-server' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation and to print more detailed information why a fallback image was necessary).
➜  libs ls
eureka-server  eureka-server-1.0-SNAPSHOT.jar
➜  libs ./eureka-server 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.6.RELEASE)

2020-06-06 13:55:03.608  INFO 25975 --- [           main] v.w.p.e.server.EurekaServerApplication   : No active profile set, falling back to default profiles: default
......
2020-06-06 13:55:06.926  INFO 25975 --- [           main] v.w.p.e.server.EurekaServerApplication   : Started EurekaServerApplication in 4.201 seconds (JVM running for 4.758)

虽然构建出来可以运行,但是警告信息中也提醒了,这是一个 Fallback Image,仍然依赖 JVM 运行。

指定不使用 Fallback Image

如果通过 --no-fallback 指定不使用 Fallback Image,则构建过程中会报错:

➜  libs $GRAALVM_HOME/bin/native-image -jar eureka-server-1.0-SNAPSHOT.jar eureka-server --no-fallback
Build on Server(pid: 29364, port: 34701)
[eureka-server:29364]    classlist:      84.36 ms,  5.18 GB
[eureka-server:29364]        (cap):     255.60 ms,  5.18 GB
[eureka-server:29364]        setup:     538.53 ms,  5.18 GB
[eureka-server:29364]     (clinit):     123.71 ms,  5.18 GB
[eureka-server:29364]   (typeflow):   5,010.03 ms,  5.18 GB
[eureka-server:29364]    (objects):   7,302.70 ms,  5.18 GB
[eureka-server:29364]   (features):     131.10 ms,  5.18 GB
[eureka-server:29364]     analysis:  12,819.24 ms,  5.18 GB
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Detailed message:
Trace: 
	at parsing java.net.URL.setURLStreamHandlerFactory(URL.java:1213)
Call path from entry point to java.net.URL.setURLStreamHandlerFactory(URLStreamHandlerFactory): 
	at java.net.URL.setURLStreamHandlerFactory(URL.java:1205)
	at org.springframework.boot.loader.jar.JarFile.resetCachedUrlHandlers(JarFile.java:408)
	at org.springframework.boot.loader.jar.JarFile.registerUrlProtocolHandler(JarFile.java:398)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:49)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52)
	at com.oracle.svm.core.JavaMainWrapper.runCore(JavaMainWrapper.java:149)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:184)
	at com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_5087f5482cc9a6abc971913ece43acb471d2631b(generated:0)

Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Error: Image build request failed with exit status 1
指定运行时才报告错误

通过 --report-unsupported-elements-at-runtime 指定运行期间才抛出异常,则可以编译出可执行程序,但执行会报错:

➜  libs $GRAALVM_HOME/bin/native-image -jar eureka-server-1.0-SNAPSHOT.jar eureka-server --no-fallback --report-unsupported-elements-at-runtime 
Build on Server(pid: 29364, port: 34701)
[eureka-server:29364]    classlist:      88.63 ms,  5.18 GB
[eureka-server:29364]        (cap):     263.34 ms,  5.18 GB
[eureka-server:29364]        setup:     533.79 ms,  5.18 GB
[eureka-server:29364]     (clinit):     165.56 ms,  5.18 GB
[eureka-server:29364]   (typeflow):   5,307.18 ms,  5.18 GB
[eureka-server:29364]    (objects):   7,254.71 ms,  5.18 GB
[eureka-server:29364]   (features):     121.96 ms,  5.18 GB
[eureka-server:29364]     analysis:  13,087.96 ms,  5.18 GB
[eureka-server:29364]     universe:     223.50 ms,  5.18 GB
[eureka-server:29364]      (parse):     418.09 ms,  5.18 GB
[eureka-server:29364]     (inline):     679.36 ms,  5.12 GB
[eureka-server:29364]    (compile):   6,067.78 ms,  5.19 GB
[eureka-server:29364]      compile:   7,730.92 ms,  5.19 GB
[eureka-server:29364]        image:     859.48 ms,  5.19 GB
[eureka-server:29364]        write:     175.21 ms,  5.19 GB
[eureka-server:29364]      [total]:  22,735.96 ms,  5.19 GB

➜  libs ./eureka-server 
Exception in thread "main" java.lang.IllegalStateException: java.util.zip.ZipException: zip END header not found
	at org.springframework.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:42)
	at org.springframework.boot.loader.JarLauncher.<init>(JarLauncher.java:36)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52)
Caused by: java.util.zip.ZipException: zip END header not found
	at java.util.zip.ZipFile$Source.zerror(ZipFile.java:1535)
	at java.util.zip.ZipFile$Source.findEND(ZipFile.java:1436)
	at java.util.zip.ZipFile$Source.initCEN(ZipFile.java:1443)
	at java.util.zip.ZipFile$Source.<init>(ZipFile.java:1274)
	at java.util.zip.ZipFile$Source.get(ZipFile.java:1237)
	at java.util.zip.ZipFile$CleanableResource.<init>(ZipFile.java:727)
	at java.util.zip.ZipFile$CleanableResource.get(ZipFile.java:55)
	at java.util.zip.ZipFile.<init>(ZipFile.java:247)
	at java.util.zip.ZipFile.<init>(ZipFile.java:177)
	at java.util.jar.JarFile.<init>(JarFile.java:348)
	at java.util.jar.JarFile.<init>(JarFile.java:319)
	at java.util.jar.JarFile.<init>(JarFile.java:285)
	at org.springframework.boot.loader.jar.JarFile.<init>(JarFile.java:119)
	at org.springframework.boot.loader.jar.JarFile.<init>(JarFile.java:114)
	at org.springframework.boot.loader.jar.JarFile.<init>(JarFile.java:100)
	at org.springframework.boot.loader.jar.JarFile.<init>(JarFile.java:91)
	at org.springframework.boot.loader.archive.JarFileArchive.<init>(JarFileArchive.java:61)
	at org.springframework.boot.loader.archive.JarFileArchive.<init>(JarFileArchive.java:57)
	at org.springframework.boot.loader.Launcher.createArchive(Launcher.java:127)
	at org.springframework.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:39)
	... 2 more

压缩类的异常,本人判断是 Native Image 没有考虑到 Spring Boot 这种 “Jar 中 Jar” 的结构

基于 class 文件和指定 classpath 构建 Native Image
/usr/local/graalvm-ee-java11-20.1.0/bin/native-image vip.wuweijie.project.eureka.server.EurekaServerApplication -cp /home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/antlr-2.7.7.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/antlr-runtime-3.4.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/aopalliance-1.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/archaius-core-0.7.6.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/aspectjweaver-1.9.5.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/aws-java-sdk-autoscaling-1.11.415.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/aws-java-sdk-core-1.11.415.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/aws-java-sdk-ec2-1.11.415.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/aws-java-sdk-route53-1.11.415.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/aws-java-sdk-sts-1.11.415.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/bcpkix-jdk15on-1.64.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/bcprov-jdk15on-1.64.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/checker-compat-qual-2.5.5.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/classmate-1.5.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/commons-codec-1.13.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/commons-collections-3.2.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/commons-configuration-1.8.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/commons-jxpath-1.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/commons-lang-2.6.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/commons-math-2.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/error_prone_annotations-2.3.4.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/eureka-client-1.9.17.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/eureka-core-1.9.17.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/evictor-1.0.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/failureaccess-1.0.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/FastInfoset-1.2.16.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/freemarker-2.3.30.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/gson-2.8.6.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/guava-28.2-android.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/guice-4.1.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/HdrHistogram-2.1.11.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/hibernate-validator-6.0.18.Final.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/httpclient-4.5.12.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/httpcore-4.4.13.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/hystrix-core-1.5.18.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/ion-java-1.0.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/istack-commons-runtime-3.0.8.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/j2objc-annotations-1.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-annotations-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-core-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-databind-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-dataformat-cbor-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-dataformat-xml-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-datatype-jdk8-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-datatype-jsr310-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-module-jaxb-annotations-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jackson-module-parameter-names-2.10.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jakarta.activation-api-1.2.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jakarta.annotation-api-1.3.5.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jakarta.validation-api-2.0.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jakarta.xml.bind-api-2.3.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/javax.inject-1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jaxb-runtime-2.3.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jboss-logging-3.4.1.Final.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jersey-apache-client4-1.19.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jersey-client-1.19.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jersey-core-1.19.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jersey-server-1.19.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jersey-servlet-1.19.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jettison-1.3.7.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jmespath-java-1.11.415.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/joda-time-2.10.5.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jsr305-3.0.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jsr311-api-1.1.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/jul-to-slf4j-1.7.30.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/LatencyUtils-2.0.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/log4j-api-2.12.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/log4j-to-slf4j-2.12.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/logback-classic-1.2.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/logback-core-1.2.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/micrometer-core-1.3.6.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/netflix-commons-util-0.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/netflix-eventbus-0.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/netflix-infix-0.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/netflix-statistics-0.1.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/reactive-streams-1.0.3.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/reactor-core-3.3.4.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/reactor-extra-3.3.3.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/ribbon-2.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/ribbon-core-2.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/ribbon-eureka-2.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/ribbon-httpclient-2.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/ribbon-loadbalancer-2.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/ribbon-transport-2.3.0.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/rxjava-1.3.8.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/rxnetty-0.4.9.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/rxnetty-contexts-0.4.9.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/rxnetty-servo-0.4.9.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/servo-core-0.12.21.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/slf4j-api-1.7.30.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/snakeyaml-1.25.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-aop-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-beans-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-actuator-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-actuator-autoconfigure-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-autoconfigure-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-actuator-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-aop-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-cache-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-freemarker-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-json-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-logging-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-tomcat-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-validation-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-boot-starter-web-2.2.6.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-commons-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-context-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-loadbalancer-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-netflix-archaius-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-netflix-eureka-client-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-netflix-eureka-server-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-netflix-hystrix-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-netflix-ribbon-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-starter-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-starter-loadbalancer-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-starter-netflix-archaius-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-starter-netflix-eureka-server-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-cloud-starter-netflix-ribbon-2.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-context-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-context-support-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-core-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-expression-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-jcl-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-security-crypto-5.2.2.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-security-rsa-1.0.9.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-web-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/spring-webmvc-5.2.5.RELEASE.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/stax2-api-4.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/stax-api-1.0.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/stax-ex-1.8.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/stringtemplate-3.2.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/tomcat-embed-core-9.0.33.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/tomcat-embed-el-9.0.33.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/tomcat-embed-websocket-9.0.33.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/txw2-2.3.2.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/woodstox-core-6.1.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/xmlpull-1.1.3.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/xpp3_min-1.1.4c.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/libs/deps/xstream-1.4.11.1.jar:/home/sia/IdeaProjects/hello-world/eureka-server/build/classes/java/main --no-fallback --report-unsupported-elements-at-runtime
Shutdown Server(pid: 29364, port: 34701)
Build on Server(pid: 11904, port: 41579)*
[vip.wuweijie.project.eureka.server.eurekaserverapplication:11904]    classlist:  11,353.59 ms,  2.69 GB
[vip.wuweijie.project.eureka.server.eurekaserverapplication:11904]        (cap):     571.36 ms,  2.69 GB
[vip.wuweijie.project.eureka.server.eurekaserverapplication:11904]        setup:   2,316.11 ms,  2.69 GB
[vip.wuweijie.project.eureka.server.eurekaserverapplication:11904]     analysis:   7,777.10 ms,  2.70 GB
Error: Classes that should be initialized at run time got initialized during image building:
 org.springframework.util.unit.DataSize was unintentionally initialized at build time. To see why org.springframework.util.unit.DataSize got initialized use -H:+TraceClassInitialization

Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Error: Image build request failed with exit status 1
make: *** [build-native] Error 1
/home/sia/IdeaProjects/hello-world/eureka-server/Makefile:14: recipe for target 'build-native' failed

这就触及本人的知识盲区了,本文不再深入研究。

添加各项追踪参数的完整日志请见:https://paste.ubuntu.com/p/HZzyhR7bSQ/


体验 Quarkus

初始化 Quarkus 项目

Quarkus 提供了一个类似于 Spring Initializr 的项目初始化方式:https://code.quarkus.io/

任选一种初始化方式:

以下操作基于 IntelliJ IDEA。

可选构建工具:

  • Maven
  • Gradle (Preview)

选择依赖:

  • RESTEasy JAX-RS 默认选择
  • 数据库相关
  • Spring 相关(Preview 阶段,选来体验一下)

项目初始化后且依赖下载完成后,可以使用 ./mvnw compile quarkus:dev 运行项目,或者通过 GUI:

Quarkus 启动:

在浏览器打开 http://localhost:8080/

项目初始化完成。

数据库准备

create table t_order
(
	id bigserial not null
		constraint t_order_pk
			primary key,
	order_time timestamp default CURRENT_TIMESTAMP not null,
	person_name varchar default ''::character varying not null,
	order_type varchar default 'None'::character varying not null,
	remark varchar
);

alter table t_order owner to postgres;

INSERT INTO public.t_order (id, order_time, person_name, order_type, remark) VALUES (1, '2020-06-06 16:31:16.000000', '烫烫烫', 'None', null);
INSERT INTO public.t_order (id, order_time, person_name, order_type, remark) VALUES (2, '2020-06-06 16:31:51.000000', '锟斤拷', 'Dinner', 'Double');

基于 Spring 体系的 CRUD 代码

Quarkus 提供了 Spring 的兼容。

Order.java 节选,忽略 getter / setter:

package vip.wuweijie.hello.quarkus.entity;

import javax.persistence.*;
import java.io.Serializable;
import java.sql.Timestamp;

/**
 * @author wuweijie
 */
@Entity
public class Order implements Serializable {
    @Id
    private Long id;
    @Column(insertable = false)
    private Timestamp orderTime;
    private String personName;
    private String orderType;
    private String remark;
}

OrderRepository.java

package vip.wuweijie.hello.quarkus.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import vip.wuweijie.hello.quarkus.entity.Order;

/**
 * @author wuweijie
 */
public interface OrderRepository extends JpaRepository<Order, Long> {
}

OrderResource.java

package vip.wuweijie.hello.quarkus;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import vip.wuweijie.hello.quarkus.entity.Order;
import vip.wuweijie.hello.quarkus.repository.OrderRepository;

import java.util.List;

/**
 * @author wuweijie
 */
@RestController
@RequestMapping("/order")
public class OrderResource {
    @Autowired
    private OrderRepository orderRepository;
    @GetMapping
    public List<Order> getOrders() {
        return orderRepository.findAll();
    }
    @PostMapping
    public Order addOrder(@RequestBody Order order) {
        return orderRepository.saveAndFlush(order);
    }
}

在 Dev 模式运行验证

请求:

GET http://localhost:8080/order
###

POST http://localhost:8080/order
Content-Type: application/json

{
  "orderTime": 1591432276000,
  "personName": "POST",
  "orderType": "nil",
  "remark": "From Post"
}

先执行一次查询:

GET http://localhost:8080/order

HTTP/1.1 200 OK
Content-Length: 287
Content-Type: application/json

[
  {
    "id": 1,
    "orderTime": 1591432276000,
    "personName": "烫烫烫",
    "orderType": "None",
    "remark": null
  },
  {
    "id": 2,
    "orderTime": 1591432311000,
    "personName": "锟斤拷",
    "orderType": "Dinner",
    "remark": "Double"
  }
]

执行一次插入,返回数据:

{"id":3,"orderTime":1591432276000,"personName":"POST","orderType":"nil","remark":"From Post"}

再次查询数据:

GET http://localhost:8080/order

HTTP/1.1 200 OK
Content-Length: 287
Content-Type: application/json

[
  {
    "id": 1,
    "orderTime": 1591432276000,
    "personName": "烫烫烫",
    "orderType": "None",
    "remark": null
  },
  {
    "id": 2,
    "orderTime": 1591432311000,
    "personName": "锟斤拷",
    "orderType": "Dinner",
    "remark": "Double"
  },
  {
    "id": 3,
    "orderTime": 1591434989319,
    "personName": "POST",
    "orderType": "nil",
    "remark": "From Post"
  }
]

Dev 模式下的 Hot Replace

如果检测到代码文件变化,则会重新加载代码:

2020-06-06 17:16:28,560 INFO  [io.qua.dep.dev] (vert.x-worker-thread-1) Changed source files detected, recompiling [/home/sia/IdeaProjects/hello-quarkus/src/main/java/vip/wuweijie/hello/quarkus/entity/Order.java]
2020-06-06 17:16:28,950 INFO  [io.quarkus] (Quarkus Main Thread) hello-quarkus stopped in 0.030s
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-06-06 17:16:29,130 INFO  [io.qua.arc.pro.BeanProcessor] (build-26) Found unrecommended usage of private members (use package-private instead) in application beans:
	- @Inject field vip.wuweijie.hello.quarkus.OrderResource#orderRepository
2020-06-06 17:16:29,220 INFO  [io.agr.pool] (Quarkus Main Thread) Datasource '<default>': Initial size smaller than min. Connections will be created when necessary
2020-06-06 17:16:29,258 INFO  [io.quarkus] (Quarkus Main Thread) hello-quarkus 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.0.Final) started in 0.301s. Listening on: http://0.0.0.0:8080
2020-06-06 17:16:29,259 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2020-06-06 17:16:29,259 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, mutiny, narayana-jta, resteasy, resteasy-jackson, spring-boot-properties, spring-data-jpa, spring-di, spring-web]
2020-06-06 17:16:29,259 INFO  [io.qua.dep.dev] (vert.x-worker-thread-1) Hot replace total time: 0.700s 

构建 Native Image

本示例 建议可用内存不小于 8GB ,依赖越多,构建过程所需内存越大。

执行命令

./mvnw package -Pnative

执行日志:

➜  hello-quarkus git:(master) ✗ ./mvnw package -Pnative
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------< vip.wuweijie.hello.quarkus:hello-quarkus >--------------
[INFO] Building hello-quarkus 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello-quarkus ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 2 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hello-quarkus ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 4 source files to /home/sia/IdeaProjects/hello-quarkus/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello-quarkus ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /home/sia/IdeaProjects/hello-quarkus/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ hello-quarkus ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/sia/IdeaProjects/hello-quarkus/target/test-classes
[INFO] 
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ hello-quarkus ---
[INFO] 
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running vip.wuweijie.hello.quarkus.ExampleResourceTest
2020-06-06 17:28:56,229 INFO  [io.qua.arc.pro.BeanProcessor] (build-4) Found unrecommended usage of private members (use package-private instead) in application beans:
        - @Inject field vip.wuweijie.hello.quarkus.OrderResource#orderRepository
2020-06-06 17:28:57,057 INFO  [io.agr.pool] (main) Datasource '<default>': Initial size smaller than min. Connections will be created when necessary
2020-06-06 17:28:57,446 INFO  [io.quarkus] (main) Quarkus 1.5.0.Final on JVM started in 2.301s. Listening on: http://0.0.0.0:8081
2020-06-06 17:28:57,447 INFO  [io.quarkus] (main) Profile test activated. 
2020-06-06 17:28:57,447 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, jdbc-postgresql, mutiny, narayana-jta, resteasy, resteasy-jackson, spring-boot-properties, spring-data-jpa, spring-di, spring-web]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.775 s - in vip.wuweijie.hello.quarkus.ExampleResourceTest
2020-06-06 17:28:58,615 INFO  [io.quarkus] (main) Quarkus stopped in 0.034s
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello-quarkus ---
[INFO] Building jar: /home/sia/IdeaProjects/hello-quarkus/target/hello-quarkus-1.0-SNAPSHOT.jar
[INFO] 
[INFO] --- quarkus-maven-plugin:1.5.0.Final:build (default) @ hello-quarkus ---
[INFO] [org.jboss.threads] JBoss Threads version 3.1.1.Final
[INFO] [org.hibernate.Version] HHH000412: Hibernate ORM core version 5.4.16.Final
[INFO] [io.quarkus.arc.processor.BeanProcessor] Found unrecommended usage of private members (use package-private instead) in application beans:
        - @Inject field vip.wuweijie.hello.quarkus.OrderResource#orderRepository
[INFO] [io.quarkus.deployment.pkg.steps.JarResultBuildStep] Building native image source jar: /home/sia/IdeaProjects/hello-quarkus/target/hello-quarkus-1.0-SNAPSHOT-native-image-source-jar/hello-quarkus-1.0-SNAPSHOT-runner.jar
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Building native image from /home/sia/IdeaProjects/hello-quarkus/target/hello-quarkus-1.0-SNAPSHOT-native-image-source-jar/hello-quarkus-1.0-SNAPSHOT-runner.jar
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] Running Quarkus native-image plugin on GraalVM Version 20.1.0 (Java Version 11.0.7)
[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] /usr/local/graalvm-ee-java11-20.1.0/bin/native-image -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-DCoordinatorEnvironmentBean.transactionStatusManagerEnable=false -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=1 -J-Duser.language=en -J-Dfile.encoding=UTF-8 --initialize-at-build-time= -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime -H:+JNI -jar hello-quarkus-1.0-SNAPSHOT-runner.jar -H:FallbackThreshold=0 -H:+ReportExceptionStackTraces -H:-AddAllCharsets -H:-IncludeAllTimeZones -H:EnableURLProtocols=http,https --enable-all-security-services -H:NativeLinkerOption=-no-pie --no-server -H:-UseServiceLoaderFeature -H:+StackTrace hello-quarkus-1.0-SNAPSHOT-runner
-H:IncludeAllTimeZones and -H:IncludeTimeZones are now deprecated. Native-image includes all timezonesby default.
[hello-quarkus-1.0-SNAPSHOT-runner:9103]    classlist:   6,932.73 ms,  1.19 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]        (cap):     541.19 ms,  1.68 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]        setup:   2,467.68 ms,  1.68 GB
17:29:11,362 INFO  [org.hib.Version] HHH000412: Hibernate ORM core version 5.4.16.Final
17:29:11,367 INFO  [org.hib.ann.com.Version] HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
17:29:11,384 INFO  [org.hib.dia.Dialect] HHH000400: Using dialect: io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect
17:29:23,231 INFO  [org.jbo.threads] JBoss Threads version 3.1.1.Final
WARNING GR-10238: VarHandle for static field is currently not fully supported. Static field private static volatile java.lang.System$Logger jdk.internal.event.EventHelper.securityLogger is not properly marked for Unsafe access!
[hello-quarkus-1.0-SNAPSHOT-runner:9103]     (clinit):     873.00 ms,  5.41 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]   (typeflow):  21,272.04 ms,  5.41 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]    (objects):  26,923.87 ms,  5.41 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]   (features):   1,099.55 ms,  5.41 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]     analysis:  53,824.27 ms,  5.41 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]     universe:   1,649.18 ms,  5.41 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]      (parse):   4,248.05 ms,  5.59 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]     (inline):   2,953.33 ms,  5.79 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]    (compile):  61,356.01 ms,  7.23 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]      compile:  72,475.76 ms,  7.23 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]        image:   5,032.36 ms,  7.23 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]        write:     995.36 ms,  7.23 GB
[hello-quarkus-1.0-SNAPSHOT-runner:9103]      [total]: 143,635.77 ms,  7.23 GB
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 145589ms
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:34 min
[INFO] Finished at: 2020-06-06T17:31:25+08:00
[INFO] ------------------------------------------------------------------------

构建完成后,target 目录下多了一个可执行文件。

执行后,可以看到应用的启动时间只需要 0.043s,基于 JVM 启动需要 1.730s

以上就是 Quarkus 的体验过程。

Demo 项目源码:https://github.com/TeslaCN/hello-quarkus


参考资料

GraalVM 相关:

Quarkus 相关:

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐