javascript
0.07 秒启动一个 SpringBoot 项目!
寫一段簡(jiǎn)單的 Java 程序。
通常我們想運(yùn)行它要這樣。
[root@flash?~]#?javac?Hello.java [root@flash?~]#?java?Hello hello?world但運(yùn)行起來需要 jre。
我們換一種方式來編譯這個(gè)程序,首先下載一個(gè) 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執(zhí)行完這個(gè)命令后,發(fā)現(xiàn)當(dāng)前目錄多了個(gè) hello 文件。
直接執(zhí)行它,可以成功!
[root@flash?~]#?./hello hello?world而且注意,這個(gè)是可以直接以二進(jìn)制形式運(yùn)行的,不依賴 jre。
也就是說,一個(gè) Java 程序,被這個(gè) native-image 編譯成了本地代碼!
這項(xiàng)技術(shù)來自于 GraalVM 的一個(gè)特性,在其官網(wǎng)的文檔中可以了解到,GraalVM 主要有三大特性:
通過新的 JIT 技術(shù)使 Java 程序更快運(yùn)行
多語言支持
構(gòu)建 JVM 無關(guān)的本地鏡像
這個(gè) native-image 技術(shù)就是其中的第三點(diǎn),即將 Java 代碼編譯成 JVM 無關(guān)的本地鏡像,使其可以直接以二進(jìn)制的方式運(yùn)行起來。
除了運(yùn)行方便之外,我們對(duì)比一下這倆的文件大小和啟動(dòng)時(shí)間。
[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總結(jié)個(gè)表格。
Hello.class??415B? 0.12s
hello? 10M? 0.01s
可以看出,啟動(dòng)時(shí)間大大縮短了!但文件大小卻大大增加了。
不過要知道,運(yùn)行 Hello.class 要整個(gè) jre 的支持,而運(yùn)行二進(jìn)制的 hello 卻不需要,這部分文件大小的差距,在小代碼上對(duì)比并不公平。
當(dāng)然,啟動(dòng)時(shí)間也都是毫秒級(jí)的,差距也不足以說明問題。下面我們?cè)囍眠@種方式,對(duì)比一個(gè) Spring Boot 項(xiàng)目。
有一點(diǎn)要說明的是,GraalVM 的本地編譯對(duì) Java 代碼有很多的限制,有的時(shí)候需要配合配置文件才能成功,比如不支持動(dòng)態(tài)類加載、反射、序列化等,具體可以見這里:
https://www.GraalVM.org/reference-manual/native-image/Limitations/
可是 Spring 項(xiàng)目中可是大量充斥著這些,我們需要增加好多配置文件,才能成功本地編譯一個(gè) Spring Boot 項(xiàng)目。
好在,Spring 已經(jīng)為我們考慮好這些事情了,提供了一個(gè)專門為 native 而生的 Spring Boot 依賴項(xiàng),最方便的是我們新建項(xiàng)目的時(shí)候可以直接從 start.spring.io 生成。
然后可以直接用 mvn 命令來打包一個(gè)本地鏡像。
[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同樣,我們用傳統(tǒng)的 jar 包方式打包一個(gè) jar 文件,對(duì)比一下。
這回大小已經(jīng)沒差那么多了,但仍然是二進(jìn)制的本地包大。不過這僅僅是幾乎空的 Spring Boot 項(xiàng)目,隨著項(xiàng)目依賴的包越來越多,二進(jìn)制的文件大小會(huì)越來越有優(yōu)勢(shì),這是后話了。
我們?cè)賮韺?duì)比一下啟動(dòng)速度,首先是傳統(tǒng)的 jar 包運(yùn)行。
[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 秒,已經(jīng)慢下來了,不過正常的空 Spring Boot 項(xiàng)目也就這樣。
再看看本地鏡像啟動(dòng)速度。
[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 秒!!!我還真從來沒有啟動(dòng) Spring Boot 項(xiàng)目體驗(yàn)過這么極速的狀態(tài)呢!!!容我高興一會(huì)兒。
看吧,前面的 hello world 項(xiàng)目看不出什么,現(xiàn)在的 Spring Boot 項(xiàng)目,優(yōu)勢(shì)就已經(jīng)完全出來了,啟動(dòng)速度秒殺呀!
可想而知,我們?cè)瓉韱?dòng)可能要幾分鐘才成功的 Spring Boot 項(xiàng)目,會(huì)被這個(gè) GraalVM 優(yōu)化到多少呢?想想就激動(dòng)!
不過這個(gè)我還沒有試,光是跑這個(gè) Spring Boot 空項(xiàng)目就忙活了好久,一直報(bào)各種各樣奇怪的錯(cuò)誤,等我再熟練熟練的。
剛剛也說了,想通過 GraalVM 的 native-image 功能編譯一個(gè) Java 程序,有很多限制,比如不支持動(dòng)態(tài)類加載、反射、動(dòng)態(tài)代理、JNI、序列化以及 invoke dynamic 指令等。
這是由于,AOT 這種提前編譯的技術(shù),需要一個(gè)封閉空間假設(shè),即在編譯期就能夠把運(yùn)行期所有需要的東西都準(zhǔn)備好,但 Java 的好多特性就是和這種封閉空間假設(shè)相沖突的。
Java 啟動(dòng)后隨著程序不斷運(yùn)行,JVM 將一部分代碼編譯成本地代碼,這個(gè)叫 JIT 技術(shù),它是在程序運(yùn)行起來之后不斷分析而做的編譯,所以它不受封閉空間假設(shè)的限制。
說回 GraalVM 的 AOT,比如程序中有個(gè)反射,這就屬于運(yùn)行時(shí)才會(huì)知道有這樣一個(gè) Student 類被需要的情況。
Class.forName("com.flash.Student")當(dāng)然,GraalVM 會(huì)通過掃描這些反射方法的調(diào)用,來嘗試分析用到了哪些類。
如果分析不出來,就需要程序員手動(dòng)配置,告訴 GraalVM 有哪些類要反射。
[{name:?"com.flash.Student",allDeclaredConstructors:?true,allPublicMethods:?true},{name:?"com.flash.Teacher",fileds:?[{name:?"teach"},?{name:?"talk"}],methods:?[{name:?"<init>",parameterTypes:?["char[]"]}]},//?…… ]但這樣肯定是反人性的。
自己寫的代碼和依賴還好,但如果是使用第三方組建,比如人人都用的 Spring,肯定不能由程序員來去寫這些配置文件。
那就只有讓 Spring 官方提供這些配置,讓程序員仍然是簡(jiǎn)單寫一些 maven 依賴就能把項(xiàng)目跑起來,才能把這個(gè)技術(shù)推廣出去,這也是剛剛 Spring Native 項(xiàng)目存在的意義。
今天簡(jiǎn)單給大家分享下 GraalVM 的使用,這個(gè)技術(shù)基本還沒有公司大規(guī)模在用,還達(dá)不到工業(yè)級(jí)的成熟,不過未來云原生領(lǐng)域要求小包和快速啟動(dòng)兩個(gè)特性,GraalVM 的未來說不定有大舞臺(tái)呢。
總結(jié)
以上是生活随笔為你收集整理的0.07 秒启动一个 SpringBoot 项目!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 惹毛了老婆后,老王居然本能地想按Ctrl
- 下一篇: 【万字长文】创业公司就应该技术选型 Sp