有偿问答
面经分享
技术探讨
资料领取
登录
0.07 秒启动一个 SpringBoot 项目!
社长
1年前
⋅ 499 阅读
> 作者:低并发编程 写一段简单的 Java 程序。 ``` public class Hello { public static void main(String[] args) { System.out.println("hello world"); } } ``` 通常我们想运行它要这样。 ``` [root@flash ~]# javac Hello.java [root@flash ~]# java Hello hello world ``` 但运行起来需要 jre 我们换一种方式来编译这个程序,首先下载一个 GraalVM 的 native-image 工具,然后。 ``` [root@flash ~]# native-image Hello [hello:11725] classlist: 1,031.19 ms, 0.96 GB [hello:11725] (cap): 2,624.14 ms, 0.96 GB [hello:11725] setup: 3,960.95 ms, 0.96 GB [hello:11725] (clinit): 288.49 ms, 1.72 GB [hello:11725] (typeflow): 2,642.38 ms, 1.72 GB [hello:11725] (objects): 3,803.54 ms, 1.72 GB [hello:11725] (features): 1,176.79 ms, 1.72 GB [hello:11725] analysis: 8,288.82 ms, 1.72 GB [hello:11725] universe: 909.14 ms, 1.75 GB [hello:11725] (parse): 801.67 ms, 1.75 GB [hello:11725] (inline): 1,096.07 ms, 2.32 GB [hello:11725] (compile): 7,352.50 ms, 2.37 GB [hello:11725] compile: 10,146.59 ms, 2.37 GB [hello:11725] image: 1,639.93 ms, 2.37 GB [hello:11725] write: 682.24 ms, 2.37 GB [hello:11725] [total]: 26,855.67 ms, 2.37 GB # Printing build artifacts to: .../hello.build_artifacts.txt ``` 执行完这个命令后,发现当前目录多了个 hello 文件。 直接执行它,可以成功! ``` [root@flash ~]# ./hello hello world ``` 而且注意,这个是可以直接以二进制形式运行的,不依赖 jre。 也就是说,一个 Java 程序,被这个 **native-image** 编译成了本地代码! 这项技术来自于 GraalVM 的一个特性,在其官网的文档中可以了解到,GraalVM 主要有三大特性: 1. 通过新的 JIT 技术使 Java 程序更快运行 2. 多语言支持 3. 构建 JVM 无关的本地镜像 这个 native-image 技术就是其中的第三点,**即将 Java 代码编译成 JVM 无关的本地镜像**,使其可以直接以二进制的方式运行起来。 除了运行方便之外,我们对比一下这俩的**文件大小**和**启动时间**。 ``` [root@flash ~]# ll -rw-r--r-- 1 flash staff 415B 10 27 15:50 Hello.class -rwxr-xr-x 1 flash staff 10M 10 27 15:51 hello [root@flash ~]# time java Hello hello world java Hello 0.09s user 0.03s system 113% cpu 0.106 total [root@flash ~]# time ./hello hello world ./hello 0.00s user 0.01s system 34% cpu 0.032 total ``` 总结个表格。 Hello.class 415B 0.12shello 10M 0.01s **可以看出,启动时间大大缩短了!但文件大小却大大增加了。** 不过要知道,运行 Hello.class 要整个 jre 的支持,而运行二进制的 hello 却不需要,这部分文件大小的差距,在小代码上对比并不公平。 当然,启动时间也都是毫秒级的,差距也不足以说明问题。下面我们试着用这种方式,对比一个 Spring Boot 项目。 有一点要说明的是,GraalVM 的本地编译对 Java 代码有很多的限制,有的时候需要配合配置文件才能成功,比如不支持动态类加载、反射、序列化等,具体可以见这里: https://www.GraalVM.org/reference-manual/native-image/Limitations/ 可是 Spring 项目中可是大量充斥着这些,我们需要增加好多配置文件,才能成功本地编译一个 Spring Boot 项目。 好在,Spring 已经为我们考虑好这些事情了,提供了一个专门为 native 而生的 Spring Boot 依赖项,最方便的是我们新建项目的时候可以直接从 **start.spring.io** 生成。 ![MarkerHub](https://image-1300566513.cos.ap-guangzhou.myqcloud.com/upload/images/20220114/3fd1a03ebfb842049db3378ce29d6cd8.png) 然后可以直接用 mvn 命令来打包一个本地镜像。 ``` [root@flash ~]# mvn package -Pnative ... [INFO] Executing: ...native-image -cp ... -H:Name=demo-1 ... [demo-1:7725] classlist: 1,695.81 ms, 0.94 GB [demo-1:7725] (cap): 1,932.48 ms, 0.94 GB [demo-1:7725] setup: 3,287.65 ms, 0.94 GB [demo-1:7725] (clinit): 2,256.61 ms, 5.68 GB [demo-1:7725] (typeflow): 18,462.41 ms, 5.68 GB [demo-1:7725] (objects): 17,848.47 ms, 5.68 GB [demo-1:7725] (features): 4,646.24 ms, 5.68 GB [demo-1:7725] analysis: 45,521.71 ms, 5.68 GB [demo-1:7725] universe: 2,624.03 ms, 5.68 GB [demo-1:7725] (parse): 1,917.71 ms, 5.68 GB [demo-1:7725] (inline): 6,021.71 ms, 5.93 GB [demo-1:7725] (compile): 30,497.99 ms, 6.06 GB [demo-1:7725] compile: 42,184.66 ms, 6.06 GB [demo-1:7725] image: 8,700.31 ms, 5.90 GB [demo-1:7725] write: 1,647.51 ms, 5.90 GB [demo-1:7725] [total]: 106,412.95 ms, 5.90 GB # Printing build artifacts to: .../demo-1.build_artifacts.txt ``` 同样,我们用传统的 jar 包方式打包一个 jar 文件,对比一下。 ``` -rwxr-xr-x 1 flash staff 66M Nov 2 16:11 demo-1 -rw-r--r-- 1 flash staff 17M Nov 2 16:09 demo-1-exec.jar ``` 这回大小已经没差那么多了,但仍然是二进制的本地包大。不过这仅仅是几乎空的 Spring Boot 项目,随着项目依赖的包越来越多,二进制的文件大小会越来越有优势,这是后话了。 我们再来对比一下**启动速度**,首先是传统的 jar 包运行。 ``` [root@flash ~]# java -jar demo-1-exec.jar . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.5.6) 2021-11-02 16:36:11.192 INFO 9468 --- [main] com.example.demo1.Demo1Application : Starting Demo1Application v0.0.1-SNAPSHOT using Java 11.0.12 on sunyiming07deMacBook-Pro.local with PID 9468 (/Users/sunyiming07/IdeaProjects/graalvm-demos/springboot/demo/demo-1/target/demo-1-0.0.1-SNAPSHOT-exec.jar started by sunyiming07 in /Users/sunyiming07/IdeaProjects/graalvm-demos/springboot/demo/demo-1/target) 2021-11-02 16:36:11.195 INFO 9468 --- [main] com.example.demo1.Demo1Application : No active profile set, falling back to default profiles: default 2021-11-02 16:36:12.097 INFO 9468 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2021-11-02 16:36:12.110 INFO 9468 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2021-11-02 16:36:12.110 INFO 9468 --- [main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.54] 2021-11-02 16:36:12.164 INFO 9468 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2021-11-02 16:36:12.164 INFO 9468 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 917 ms 2021-11-02 16:36:12.484 INFO 9468 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2021-11-02 16:36:12.494 INFO 9468 --- [main] com.example.demo1.Demo1Application : Started Demo1Application in 2.033 seconds (JVM running for 2.504) ``` 2.033 秒,已经慢下来了,不过正常的空 Spring Boot 项目也就这样。 再看看本地镜像启动速度。 ``` [root@flash ~]# ./demo-1 2021-11-02 16:38:33.141 INFO 9724 --- [main] o.s.nativex.NativeListener : This application is bootstrapped with code generated with Spring AOT . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.5.6) 2021-11-02 16:38:33.143 INFO 9724 --- [main] com.example.demo1.Demo1Application : Starting Demo1Application v0.0.1-SNAPSHOT using Java 11.0.12 on sunyiming07deMacBook-Pro.local with PID 9724 (/Users/sunyiming07/IdeaProjects/graalvm-demos/springboot/demo/demo-1/target/demo-1 started by sunyiming07 in /Users/sunyiming07/IdeaProjects/graalvm-demos/springboot/demo/demo-1/target) 2021-11-02 16:38:33.143 INFO 9724 --- [main] com.example.demo1.Demo1Application : No active profile set, falling back to default profiles: default 2021-11-02 16:38:33.178 INFO 9724 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2021-11-02 16:38:33.178 INFO 9724 --- [main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2021-11-02 16:38:33.178 INFO 9724 --- [main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.54] 2021-11-02 16:38:33.184 INFO 9724 --- [main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2021-11-02 16:38:33.184 INFO 9724 --- [main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 41 ms 2021-11-02 16:38:33.204 INFO 9724 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2021-11-02 16:38:33.204 INFO 9724 --- [main] com.example.demo1.Demo1Application : Started Demo1Application in 0.078 seconds (JVM running for 0.08) ``` ``` 我去!0.078 秒!!! ``` 我还真从来没有启动 Spring Boot 项目体验过这么极速的状态呢!!!容我高兴一会儿。 看吧,前面的 hello world 项目看不出什么,现在的 Spring Boot 项目,优势就已经完全出来了,启动速度秒杀呀! 可想而知,我们原来启动可能要几分钟才成功的 Spring Boot 项目,会被这个 GraalVM 优化到多少呢?想想就激动! 不过这个我还没有试,光是跑这个 Spring Boot 空项目就忙活了好久,一直报各种各样奇怪的错误,等我再熟练熟练的。 刚刚也说了,想通过 GraalVM 的 native-image 功能编译一个 Java 程序,有很多限制,比如不支持动态类加载、反射、动态代理、JNI、序列化以及 invoke dynamic 指令等。 这是由于,**AOT **这种提前编译的技术,需要一个**封闭空间假设**,即在编译期就能够把运行期所有需要的东西都准备好,但 Java 的好多特性就是和这种封闭空间假设相冲突的。 Java 启动后随着程序不断运行,JVM 将一部分代码编译成本地代码,这个叫 **JIT **技术,它是在程序运行起来之后不断分析而做的编译,所以它不受封闭空间假设的限制。 说回 GraalVM 的 AOT,比如程序中有个反射,这就属于运行时才会知道有这样一个 Student 类被需要的情况。 ``` Class.forName("com.flash.Student") ``` 当然,GraalVM 会通过扫描这些反射方法的调用,来尝试分析用到了哪些类。 如果分析不出来,就需要程序员手动配置,告诉 GraalVM 有哪些类要反射。 ``` [ { name: "com.flash.Student", allDeclaredConstructors: true, allPublicMethods: true }, { name: "com.flash.Teacher", fileds: [{name: "teach"}, {name: "talk"}], methods: [{ name: "
", parameterTypes: ["char[]"] }] }, // …… ] ``` 但这样肯定是反人性的。 自己写的代码和依赖还好,但如果是使用第三方组建,比如人人都用的 Spring,肯定不能由程序员来去写这些配置文件。 那就只有让 Spring 官方提供这些配置,让程序员仍然是简单写一些 maven 依赖就能把项目跑起来,才能把这个技术推广出去,这也是刚刚 **Spring Native** 项目存在的意义。 今天简单给大家分享下 GraalVM 的使用,这个技术基本还没有公司大规模在用,还达不到工业级的成熟,不过未来云原生领域要求**小包**和**快速启动**两个特性,GraalVM 的未来说不定有大舞台呢。
阅读全部
全部评论:
0
条
我有话说:
@
发送
-- 目录 --
关注官方公众号:
Java问答社
接收最新有赏问答推送!
最新发布
1.
SpringBoot 接口数据加解密技巧,so easy!
2.
一个依赖搞定 Spring Boot 反爬虫,防止接口盗刷!
3.
Java8 Stream 极大简化了代码,它是如何实现的?
4.
马上大四了,秋招还是春招好?先找工作还是找实习?
5.
万字详解 Linux 常用指令(值得收藏)
6.
4年工作经验,多线程间的5种通信方式都说不出来,你敢信?
最新评论
部署文档没有了,您能提供下吗
部署文档没有了,能提供下吗
我测你的🐎
源码从哪里获取请问
想学
那篇石墨文档 没有权限查看哇