亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

引言

網(wǎng)上每隔一段時間就能見到幾條“未來X語言將會取代JAVA”的新聞,此處“X”可以用Kotlin、Golang、Dart、JavaScript、Python……等各種編程語言來代入。這大概就是長期占據(jù)編程語言榜單第一位的煩惱,天下第一總避免不了挑戰(zhàn)者相伴。

如果Java有擬人化的思維,它應(yīng)該從來沒有懼怕過被哪一門語言所取代,Java“天下第一”的底氣不在于語法多么先進(jìn)好用,而是來自它龐大的用戶群和極其成熟的軟件生態(tài),這在朝夕之間難以撼動。不過,既然有那么多新、舊編程語言的興起躁動,說明必然有其需求動力所在,譬如互聯(lián)網(wǎng)之于JavaScript、人工智能之于Python,微服務(wù)風(fēng)潮之于Golang等等。大家都清楚不太可能有哪門語言能在每一個領(lǐng)域都盡占優(yōu)勢,Java已是距離這個目標(biāo)最接近的選項,但若“天下第一”還要百尺竿頭更進(jìn)一步的話,似乎就只能忘掉Java語言本身,踏入無招勝有招的境界。

Graal VM的誕生

2018年4月,Oracle Labs新公開了一項黑科技:Graal VM,從它的口號“Run Programs Faster Anywhere”就能感覺到一顆蓬勃的野心,這句話顯然是與1995年Java剛誕生時的“Write Once,Run Anywhere”在遙相呼應(yīng)。

Graal VM:云原生時代的Java

Graal VM

Graal VM被官方稱為“Universal VM”和“Polyglot VM”,這是一個在HotSpot虛擬機(jī)基礎(chǔ)上增強(qiáng)而成的跨語言全棧虛擬機(jī),可以作為“任何語言”的運(yùn)行平臺使用,這里“任何語言”包括了Java、Scala、Groovy、Kotlin等基于Java虛擬機(jī)之上的語言,還包括了C、C++、Rust等基于LLVM的語言,同時支持其他像JavaScript、Ruby、Python和R語言等等。Graal VM可以無額外開銷地混合使用這些編程語言,支持不同語言中混用對方的接口和對象,也能夠支持這些語言使用已經(jīng)編寫好的本地庫文件。

Graal VM的基本工作原理是將這些語言的源代碼(例如JavaScript)或源代碼編譯后的中間格式(例如LLVM字節(jié)碼)通過解釋器轉(zhuǎn)換為能被Graal VM接受的中間表示(Intermediate Representation,IR),譬如設(shè)計一個解釋器專門對LLVM輸出的字節(jié)碼進(jìn)行轉(zhuǎn)換來支持C和C++語言,這個過程稱為“程序特化”(Specialized,也常稱為Partial Evaluation)。Graal VM提供了Truffle工具集來快速構(gòu)建面向一種新語言的解釋器,并用它構(gòu)建了一個稱為Sulong的高性能LLVM字節(jié)碼解釋器。

以更嚴(yán)格的角度來看,Graal VM才是真正意義上與物理計算機(jī)相對應(yīng)的高級語言虛擬機(jī),理由是它與物理硬件的指令集一樣,做到了只與機(jī)器特性相關(guān)而不與某種高級語言特性相關(guān)。Oracle Labs的研究總監(jiān)Thomas Wuerthinger在接受InfoQ采訪時談到:“隨著Graal VM 1.0的發(fā)布,我們已經(jīng)證明了擁有高性能的多語言虛擬機(jī)是可能的,并且實現(xiàn)這個目標(biāo)的最佳方式不是通過類似Java虛擬機(jī)和微軟CLR那樣帶有語言特性的字節(jié)碼”。對于一些本來就不以速度見長的語言運(yùn)行環(huán)境,由于Graal VM本身能夠?qū)斎氲闹虚g表示進(jìn)行自動優(yōu)化,在運(yùn)行時還能進(jìn)行即時編譯優(yōu)化,往往使用Graal VM實現(xiàn)能夠獲得比原生編譯器更優(yōu)秀的執(zhí)行效率,譬如Graal.js要優(yōu)于Node.js、Graal.Python要優(yōu)于CPtyhon,TruffleRuby要優(yōu)于Ruby MRI,F(xiàn)astR要優(yōu)于R語言等等。

針對Java而言,Graal VM本來就是在HotSpot基礎(chǔ)上誕生的,天生就可作為一套完整的符合Java SE 8標(biāo)準(zhǔn)Java虛擬機(jī)來使用。它和標(biāo)準(zhǔn)的HotSpot差異主要在即時編譯器上,其執(zhí)行效率、編譯質(zhì)量目前與標(biāo)準(zhǔn)版的HotSpot相比也是互有勝負(fù)。但現(xiàn)在Oracle Labs和美國大學(xué)里面的研究院所做的最新即時編譯技術(shù)的研究全部都遷移至基于Graal VM之上進(jìn)行了,其發(fā)展?jié)摿α钊似诖?。如果Java語言或者HotSpot虛擬機(jī)真的有被取代的一天,那從現(xiàn)在看來Graal VM是希望最大的一個候選項,這場革命很可能會在Java使用者沒有明顯感覺的情況下悄然而來,Java世界所有的軟件生態(tài)都沒有發(fā)生絲毫變化,但天下第一的位置已經(jīng)悄然更迭。

新一代即時編譯器

對需要長時間運(yùn)行的應(yīng)用來說,由于經(jīng)過充分預(yù)熱,熱點代碼會被HotSpot的探測機(jī)制準(zhǔn)確定位捕獲,并將其編譯為物理硬件可直接執(zhí)行的機(jī)器碼,在這類應(yīng)用中Java的運(yùn)行效率很大程度上是取決于即時編譯器所輸出的代碼質(zhì)量。

HotSpot虛擬機(jī)中包含有兩個即時編譯器,分別是編譯時間較短但輸出代碼優(yōu)化程度較低的客戶端編譯器(簡稱為C1)以及編譯耗時長但輸出代碼優(yōu)化質(zhì)量也更高的服務(wù)端編譯器(簡稱為C2),通常它們會在分層編譯機(jī)制下與解釋器互相配合來共同構(gòu)成HotSpot虛擬機(jī)的執(zhí)行子系統(tǒng)的。

自JDK 10起,HotSpot中又加入了一個全新的即時編譯器:Graal編譯器,看名字就可以聯(lián)想到它是來自于前一節(jié)提到的Graal VM。Graal編譯器是作為C2編譯器替代者的角色登場的。C2的歷史已經(jīng)非常長了,可以追溯到Cliff Click大神讀博士期間的作品,這個由C++寫成的編譯器盡管目前依然效果拔群,但已經(jīng)復(fù)雜到連Cliff Click本人都不愿意繼續(xù)維護(hù)的程度。而Graal編譯器本身就是由Java語言寫成,實現(xiàn)時又刻意與C2采用了同一種名為“Sea-of-Nodes”的高級中間表示(High IR)形式,使其能夠更容易借鑒C2的優(yōu)點。Graal編譯器比C2編譯器晚了足足二十年面世,有著極其充沛的后發(fā)優(yōu)勢,在保持能輸出相近質(zhì)量的編譯代碼的同時,開發(fā)效率和擴(kuò)展性上都要顯著優(yōu)于C2編譯器,這決定了C2編譯器中優(yōu)秀的代碼優(yōu)化技術(shù)可以輕易地移植到Graal編譯器上,但是反過來Graal編譯器中行之有效的優(yōu)化在C2編譯器里實現(xiàn)起來則異常艱難。這種情況下,Graal的編譯效果短短幾年間迅速追平了C2,甚至某些測試項中開始逐漸反超C2編譯器。Graal能夠做比C2更加復(fù)雜的優(yōu)化,如“部分逃逸分析”(Partial Escape Analysis),也擁有比C2更容易使用“激進(jìn)預(yù)測性優(yōu)化”(Aggressive Speculative Optimization)的策略,支持自定義的預(yù)測性假設(shè)等等。

今天的Graal編譯器尚且年幼,還未經(jīng)過足夠多的實踐驗證,所以仍然帶著“實驗狀態(tài)”的標(biāo)簽,需要用開關(guān)參數(shù)去激活,這讓筆者不禁聯(lián)想起JDK 1.3時代,HotSpot虛擬機(jī)剛剛橫空出世時的場景,同樣也是需要用開關(guān)激活,也是作為Classic虛擬機(jī)的替代品的一段歷史。

Graal編譯器未來的前途可期,作為Java虛擬機(jī)執(zhí)行代碼的最新引擎,它的持續(xù)改進(jìn),會同時為HotSpot與Graal VM注入更快更強(qiáng)的驅(qū)動力。

向原生邁進(jìn)

對不需要長時間運(yùn)行的,或者小型化的應(yīng)用而言,Java(而不是指Java ME)天生就帶有一些劣勢,這里并不光是指跑個HelloWorld也需要百多兆的JRE之類的問題,而更重要的是指近幾年從大型單體應(yīng)用架構(gòu)向小型微服務(wù)應(yīng)用架構(gòu)發(fā)展的技術(shù)潮流下,Java表現(xiàn)出來的不適應(yīng)。

在微服務(wù)架構(gòu)的視角下,應(yīng)用拆分后,單個微服務(wù)很可能就不再需要再面對數(shù)十、數(shù)百GB乃至TB的內(nèi)存,有了高可用的服務(wù)集群,也無須追求單個服務(wù)要7×24小時不可間斷地運(yùn)行,它們隨時可以中斷和更新;但相應(yīng)地,Java的啟動時間相對較長、需要預(yù)熱才能達(dá)到最高性能等特點就顯得相悖于這樣的應(yīng)用場景。在無服務(wù)架構(gòu)中,矛盾則可能會更加突出,比起服務(wù),一個函數(shù)的規(guī)模通常會更小,執(zhí)行時間會更短,當(dāng)前最熱門的無服務(wù)運(yùn)行環(huán)境AWS Lambda所允許的最長運(yùn)行時間僅有15分鐘。

一直把軟件服務(wù)作為重點領(lǐng)域的Java自然不可能對此視而不見,在最新的幾個JDK版本的功能清單中,已經(jīng)陸續(xù)推出了跨進(jìn)程的、可以面向用戶程序的類型信息共享(Application Class Data Sharing,AppCDS,允許把加載解析后的類型信息緩存起來,從而提升下次啟動速度,原本CDS只支持Java標(biāo)準(zhǔn)庫,在JDK 10時的AppCDS開始支持用戶的程序代碼)、無操作的垃圾收集器(Epsilon,只做內(nèi)存分配而不做回收的收集器,對于運(yùn)行完就退出的應(yīng)用十分合適)等改善措施。而醞釀中的一個更徹底的解決方案,是逐步開始對提前編譯(Ahead of Time Compilation,AOT)提供支持。

提前編譯是相對于即時編譯的概念,提前編譯能帶來的最大好處是Java虛擬機(jī)加載這些已經(jīng)預(yù)編譯成二進(jìn)制庫之后就能夠直接調(diào)用,而無須再等待即時編譯器在運(yùn)行時將其編譯成二進(jìn)制機(jī)器碼。理論上,提前編譯可以減少即時編譯帶來的預(yù)熱時間,減少Java應(yīng)用長期給人帶來的“第一次運(yùn)行慢”不良體驗,可以放心地進(jìn)行很多全程序的分析行為,可以使用時間壓力更大的優(yōu)化措施。

但是提前編譯的壞處也很明顯,它破壞了Java“一次編寫,到處運(yùn)行”的承諾,必須為每個不同的硬件、操作系統(tǒng)去編譯對應(yīng)的發(fā)行包。也顯著降低了Java鏈接過程的動態(tài)性,必須要求加載的代碼在編譯期就是全部已知的,而不能再是運(yùn)行期才確定,否則就只能舍棄掉已經(jīng)提前編譯好的版本,退回到原來的即時編譯執(zhí)行狀態(tài)。

早在JDK 9時期,Java 就提供了實驗性的Jaotc命令來進(jìn)行提前編譯,不過多數(shù)人試用過后都頗感失望,大家原本期望的是類似于Excelsior JET那樣的編譯過后能生成本地代碼完全脫離Java虛擬機(jī)運(yùn)行的解決方案,但Jaotc其實僅僅是代替掉即時編譯的一部分作用而已,仍需要運(yùn)行于HotSpot之上。

直到Substrate VM出現(xiàn),才算是滿足了人們心中對Java提前編譯的全部期待。Substrate VM是在Graal VM 0.20版本里新出現(xiàn)的一個極小型的運(yùn)行時環(huán)境,包括了獨(dú)立的異常處理、同步調(diào)度、線程管理、內(nèi)存管理(垃圾收集)和JNI訪問等組件,目標(biāo)是代替HotSpot用來支持提前編譯后的程序執(zhí)行。它還包含了一個本地鏡像的構(gòu)造器(Native Image Generator)用于為用戶程序建立基于Substrate VM的本地運(yùn)行時鏡像。這個構(gòu)造器采用指針分析(Points-To Analysis)技術(shù),從用戶提供的程序入口出發(fā),搜索所有可達(dá)的代碼。在搜索的同時,它還將執(zhí)行初始化代碼,并在最終生成可執(zhí)行文件時,將已初始化的堆保存至一個堆快照之中。這樣一來,Substrate VM就可以直接從目標(biāo)程序開始運(yùn)行,而無須重復(fù)進(jìn)行Java虛擬機(jī)的初始化過程。但相應(yīng)地,原理上也決定了Substrate VM必須要求目標(biāo)程序是完全封閉的,即不能動態(tài)加載其他編譯期不可知的代碼和類庫。基于這個假設(shè),Substrate VM才能探索整個編譯空間,并通過靜態(tài)分析推算出所有虛方法調(diào)用的目標(biāo)方法。

Substrate VM帶來的好處是能顯著降低了內(nèi)存占用及啟動時間,由于HotSpot本身就會有一定的內(nèi)存消耗(通常約幾十MB),這對最低也從幾GB內(nèi)存起步的大型單體應(yīng)用來說并不算什么,但在微服務(wù)下就是一筆不可忽視的成本。根據(jù)Oracle官方給出的測試數(shù)據(jù),運(yùn)行在Substrate VM上的小規(guī)模應(yīng)用,其內(nèi)存占用和啟動時間與運(yùn)行在HotSpot相比有了5倍到50倍的下降,具體結(jié)果如下圖所示:

Graal VM:云原生時代的Java

啟動時間對比

Graal VM:云原生時代的Java

啟動時間對比

Substrate VM補(bǔ)全了Graal VM“Run Programs Faster Anywhere”愿景藍(lán)圖里最后的一塊拼圖,讓Graal VM支持其他語言時不會有重量級的運(yùn)行負(fù)擔(dān)。譬如運(yùn)行JavaScript代碼,Node.js的V8引擎執(zhí)行效率非常高,但即使是最簡單的HelloWorld,它也要使用約20MB的內(nèi)存,而運(yùn)行在Substrate VM上的Graal.js,跑一個HelloWorld則只需要4.2MB內(nèi)存而已,且運(yùn)行速度與V8持平。Substrate VM 的輕量特性,使得它十分適合于嵌入至其他系統(tǒng)之中,譬如Oracle自家的數(shù)據(jù)庫就已經(jīng)開始使用這種方式支持用不同的語言代替PL/SQL來編寫存儲過程。

沒有虛擬機(jī)的Java

盡管Java已經(jīng)看清楚了在微服務(wù)時代的前進(jìn)目標(biāo),但是,Java語言和生態(tài)在微服務(wù)、微應(yīng)用環(huán)境中的天生的劣勢并不會一蹴而就地被解決,通往這個目標(biāo)的道路注定會充滿荊棘;盡管已經(jīng)有了放棄“一次編寫,到處運(yùn)行”、放棄語言動態(tài)性的思想準(zhǔn)備,但是,這些特性并不單純是宣傳口號,它們在Java語言誕生之初就被植入到基因之中,當(dāng)Graal VM試圖打破這些規(guī)則的同時,也受到了Java語言和在其之上的生態(tài)生態(tài)的強(qiáng)烈反噬,筆者選擇其中最主要的一些困難列舉如下:

  • 某些Java語言的特性,使得Graal VM編譯本地鏡像的過程變得極為艱難。譬如常見的反射,除非使用安全管理器去專門進(jìn)行認(rèn)證許可,否則反射機(jī)制具有在運(yùn)行期動態(tài)調(diào)用幾乎所有API接口的能力,且具體會調(diào)用哪些接口,在程序不會真正運(yùn)行起來的編譯期是無法獲知的。反射顯然是Java不能放棄不能妥協(xié)的重要特性,為此,只能由程序的開發(fā)者明確地告知Graal VM有哪些代碼可能被反射調(diào)用(通過JSON配置文件的形式),Graal VM才能在編譯本地程序時將它們囊括進(jìn)來。這是一種可操作性極其低下卻又無可奈何的解決方案,即使開發(fā)者接受不厭其煩地列舉出自己代碼中所用到的反射API,但他們又如何能保證程序所引用的其他類庫的反射行為都已全部被獲知,其中沒有任何遺漏?與此類似的還有另外一些語言特性,如動態(tài)代理等。另外,一切非代碼性質(zhì)的資源,如最典型的配置文件等,也都必須明確加入配置中才能被Graal VM編譯打包。這導(dǎo)致了如果沒有專門的工具去協(xié)助,使用Graal VM編譯Java的遺留系統(tǒng)即使理論可行,實際操作也將是極度的繁瑣。

 {
name: "com.github.fenixsoft.SomeClass",allDeclaredConstructors: true,allPublicMethods: true},{name: "com.github.fenixsoft.AnotherClass",fileds: [{name: "foo"}, {name: "bar"}],methods: [{name: "<init>",parameterTypes: ["char[]"]}]},// something else ……
  • 大多數(shù)運(yùn)行期對字節(jié)碼的生成和修改操作,在Graal VM看來都是無法接受的,因為Substrate VM里面不再包含即時編譯器和字節(jié)碼執(zhí)行引擎,所以一切可能被運(yùn)行的字節(jié)碼,都必須經(jīng)過AOT編譯成為原生代碼。請不要覺得運(yùn)行期直接生成字節(jié)碼會很罕見,誤以為導(dǎo)致的影響應(yīng)該不算很大。事實上,多數(shù)實際用于生產(chǎn)的Java系統(tǒng)都或直接或講解、或多或少引用了ASM、CGLIB、Javassist這類字節(jié)碼庫。舉個例子,CGLIB是通過運(yùn)行時產(chǎn)生字節(jié)碼(生成代理類的子類)來做動態(tài)代理的,長期以來這都是Java世界里進(jìn)行類增強(qiáng)的主流形式,因為面向接口的增強(qiáng)可以使用JDK自帶的動態(tài)代理,但對類的增強(qiáng)則并沒有多少選擇的余地。CGLIB也是Spring用來做類增強(qiáng)的選擇,但Graal VM明確表示是不可能支持CGLIB的,因此,這點就必須由用戶(面向接口編程)、框架(Spring這些DI框架放棄CGLIB增強(qiáng))和Graal VM(起碼得支持JDK的動態(tài)代理,留條活路可走)來共同解決。自Spring Framework 5.2起,@Configuration注解中加入了一個新的proxyBeanMethods參數(shù),設(shè)置為false則可避免Spring對與非接口類型的Bean進(jìn)行代理。同樣地,對應(yīng)在Spring Boot 2.2中,@SpringBootApplication注解也增加了proxyBeanMethods參數(shù),通常采用Graal VM去構(gòu)建的Spring Boot本地應(yīng)用都需要設(shè)置該參數(shù)。

  • 一切HotSpot虛擬機(jī)本身的內(nèi)部接口,譬如JVMTI、JVMCI等,在都將不復(fù)存在了——在本地鏡像中,連HotSpot本身都被消滅了,這些接口自然成了無根之木。這對使用者一側(cè)的最大影響是再也無法進(jìn)行Java語言層次的遠(yuǎn)程調(diào)試了,最多只能進(jìn)行匯編層次的調(diào)試。在生產(chǎn)系統(tǒng)中一般也沒有人這樣做,開發(fā)環(huán)境就沒必要采用Graal VM編譯,這點的實際影響并不算大。

  • Graal VM放棄了一部分可以妥協(xié)的語言和平臺層面的特性,譬如Finalizer、安全管理器、InvokeDynamic指令和MethodHandles,等等,在Graal VM中都被聲明為不支持的,這些妥協(xié)的內(nèi)容大多倒并非全然無法解決,主要是基于工作量性價比的原因。能夠被放棄的語言特性,說明確實是影響范圍非常小的,所以這個對使用者來說一般是可以接受的。

……

以上,是Graal VM在Java語言中面臨的部分困難,在整個Java的生態(tài)系統(tǒng)中,數(shù)量龐大的第三方庫才是真正最棘手的難題??梢灶A(yù)料,這些第三方庫一旦脫離了Java虛擬機(jī),在原生環(huán)境中肯定會暴露出無數(shù)千奇百怪的異常行為。Graal VM團(tuán)隊對此的態(tài)度非常務(wù)實,并沒有直接硬啃。要建設(shè)可持續(xù)、可維護(hù)的Graal VM,就不能為了兼容現(xiàn)有JVM生態(tài),做出過多的會影響性能、優(yōu)化空間和未來拓展的妥協(xié)犧牲,為此,應(yīng)該也只能反過來由Java生態(tài)去適應(yīng)Graal VM,這是Graal VM團(tuán)隊明確傳遞出對第三方庫的態(tài)度:

3rd party libraries

Graal VM native support needs to be sustainable and maintainable, that's why we do not want to maintain fragile pathches for the whole JVM ecosystem. The ecosystem of libraries needs to support it natively.

—— Sébastien Deleuze,DEVOXX 2019

為了推進(jìn)Java生態(tài)向Graal VM兼容,Graal VM主動拉攏了Java生態(tài)中最龐大的一個派系:Spring。從2018年起,來自O(shè)racle的Graal VM團(tuán)隊與來自Pivotal的Spring團(tuán)隊已經(jīng)緊密合作了很長的一段時間,共同創(chuàng)建了Spring Graal Native項目來解決Spring全家桶在Graal VM上的運(yùn)行適配問題,在不久的將來(預(yù)計應(yīng)該是2020年10月左右),下一個大的Spring版本(Spring Framework 5.3、Spring Boot 2.3)的其中一項主要改進(jìn)就是能夠開箱即用地支持Graal VM,這樣,用于微服務(wù)環(huán)境的Spring Cloud便會獲得不受Java虛擬機(jī)束縛的更廣闊舞臺空間。

Spring over Graal

前面幾部分,我們以定性的角度分析了Graal VM誕生的背景與它的價值,在最后這部分,我們嘗試進(jìn)行一些實踐和定量的討論,介紹具體如何使用Graal VM之余,也希望能以更加量化的角度去理解程序運(yùn)行在Graal VM之上,會有哪些具體的收益和代價。

盡管需要到2020年10月正式發(fā)布之后,Spring對Graal VM的支持才會正式提供,但現(xiàn)在的我們其實已經(jīng)可以使用Graal VM來(實驗性地)運(yùn)行Spring、Spring Boot、Spring Data、Netty、JPA等等的一系列組件(不過SpringCloud中的組件暫時還不行)。接下來,我們將嘗試使用Graal VM來編譯一個標(biāo)準(zhǔn)的Spring Boot應(yīng)用:

 

環(huán)境準(zhǔn)備

  • 安裝Graal VM,你可以選擇直接下載安裝(版本選擇Graal VM CE 20.0.0),然后配置好PATH和JAVA_HOME環(huán)境變量即可;也可以選擇使用SDKMAN來快速切換環(huán)境。個人推薦后者,畢竟目前還不適合長期基于Graal VM環(huán)境下工作,經(jīng)常手工切換會很麻煩。

# 安裝SDKMAN
$ curl -s "https://get.sdkman.io" | bash # 安裝Graal VM $ sdk install java 20.0.0.r8-grl
  • 安裝本地鏡像編譯依賴的LLVM工具鏈。

# gu命令來源于Graal VM的bin目錄
$ gu install native-image
  • 請注意,這里已經(jīng)假設(shè)你機(jī)器上已有基礎(chǔ)的GCC編譯環(huán)境,即已安裝過build-essential、libz-dev等套件。沒有的話請先行安裝。對于windows環(huán)境來說,這步是需要Windows SDK 7.1中的C++編譯環(huán)境來支持。我個人并不建議在Windows上進(jìn)行Java應(yīng)用的本地化操作,如果說在linux中編譯一個本地鏡像,通常是為了打包到Docker,然后發(fā)布到服務(wù)器中使用。那在Windows上編譯一個本地鏡像,你打算用它來干什么呢?

 

編譯準(zhǔn)備

  • 首先,我們先假設(shè)你準(zhǔn)備編譯的代碼是“符合要求”的,即沒有使用到Graal VM不支持的特性,譬如前面提到的Finalizer、CGLIB、InvokeDynamic這類功能。然后,由于我們用的是Graal VM的Java 8版本,也必須假設(shè)你編譯使用Java語言級別在Java 8以內(nèi)。

  • 然后,我們需要用到尚未正式對外發(fā)布的Spring Boot 2.3,目前最新的版本是Spring Boot 2.3.0.M4。請將你的pom.xml中的Spring Boot版本修改如下(假設(shè)你編譯用的是Maven,用Gradle的請自行調(diào)整):

<parent>
<groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.0.M4</version><relativePath/></parent>
  • 由于是未發(fā)布的Spring Boot版本,所以它在Maven的中央倉庫中是找不到的,需要手動加入Spring的私有倉庫,如下所示:

<repositories>
<repository><id>spring-milestone</id><name>Spring milestone</name><url>https://repo.spring.io/milestone</url></repository></repositories>
  • 最后,盡管我們可以通過命令行(使用native-image命令)來直接進(jìn)行編譯,這對于沒有什么依賴的普通Jar包、寫一個Helloworld來說都是可行的,但對于Spring Boot,光是在命令行中寫Classpath上都忙活一陣的,建議還是使用Maven插件來驅(qū)動Graal VM編譯,這個插件能夠根據(jù)Maven的依賴信息自動組織好Classpath,你只需要填其他命令行參數(shù)就行了。因為并不是每次編譯都需要構(gòu)建一次本地鏡像,為了不干擾使用普通Java虛擬機(jī)的編譯,建議在Maven中獨(dú)立建一個Profile來調(diào)用Graal VM插件,具體如下所示:

<profiles>
<profile><id>graal</id><build><plugins><plugin><groupId>org.graalvm.nativeimage</groupId><artifactId>native-image-maven-plugin</artifactId><version>20.0.0</version><configuration><buildArgs>-Dspring.graal.remove-unused-autoconfig=true --no-fallback -H:+ReportExceptionStackTraces --no-server</buildArgs></configuration><executions><execution><goals><goal>native-image</goal></goals><phase>package</phase></execution></executions></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></profile></profiles>
  • 這個插件同樣在Maven中央倉庫中不存在,所以也得加上前面Spring的私有庫:

<pluginRepositories>
<pluginRepository><id>spring-milestone</id><name>Spring milestone</name><url>https://repo.spring.io/milestone</url></pluginRepository></pluginRepositories>
  • 至此,編譯環(huán)境的準(zhǔn)備順利完成。

 

程序調(diào)整

  • 首先,前面提到了Graal VM不支持CGLIB,只能使用JDK動態(tài)代理,所以應(yīng)當(dāng)把Spring對普通類的Bean增強(qiáng)給關(guān)閉掉:

@SpringBootApplication(proxyBeanMethods = false)
public class ExampleApplication {public static void main(String[] args) {SpringApplication.run(ExampleApplication.class, args);}}
  • 然后,這是最麻煩的一個步驟,你程序里反射調(diào)用過哪些API、用到哪些資源、動態(tài)代理,還有哪些類型需要在編譯期初始化的,都必須使用JSON配置文件逐一告知Graal VM。前面也說過了,這事情只有理論上的可行性,實際做起來完全不可操作。Graal VM的開發(fā)團(tuán)隊當(dāng)然也清楚這一點,所以這個步驟實際的處理途徑有兩種,第一種是假設(shè)你依賴的第三方包,全部都在Jar包中內(nèi)置了以上編譯所需的配置信息,這樣你只要提供你程序里用戶代碼中用到的配置即可,如果你程序里沒寫過反射、沒用過動態(tài)代理什么的,那就什么配置都無需提供。第二種途徑是Graal VM計劃提供一個Native Image Agent的代理,只要將它掛載在在程序中,以普通Java虛擬機(jī)運(yùn)行一遍,把所有可能的代碼路徑都操作覆蓋到,這個Agent就能自動幫你根據(jù)程序?qū)嶋H運(yùn)行情況來生成編譯所需要的配置,這樣無論是你自己的代碼還是第三方的代碼,都不需要做預(yù)先的配置。目前,第二種方式中的Agent尚未正式發(fā)布,只有方式一是可用的。幸好,Spring與Graal VM共同維護(hù)的在Spring Graal Native項目已經(jīng)提供了大多數(shù)Spring Boot組件的配置信息(以及一些需要在代碼層面處理的Patch),我們只需要簡單依賴該工程即可。

<dependencies>
<dependency><groupId>org.springframework.experimental</groupId><artifactId>spring-graal-native</artifactId><version>0.6.1.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId></dependency></dependencies>
  • 另外還有一個小問題,由于目前Spring Boot嵌入的Tomcat中,WebSocket部分在JMX反射上還有一些瑕疵,在修正該問題的PR被Merge之前,暫時需要手工去除掉這個依賴:

<dependencies>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.Apache.tomcat.embed</groupId><artifactId>tomcat-embed-websocket</artifactId></exclusion></exclusions></dependency></dependencies>
  • 最后,在Maven中給出程序的啟動類的路徑:

<properties>
<start-class>com.example.ExampleApplication</start-class></properties>

 

開始編譯

  • 到此一切準(zhǔn)備就緒,通過Maven進(jìn)行編譯:

$ mvn -Pgraal clean package
  • 編譯的結(jié)果默認(rèn)輸出在target目錄,以啟動類的名字命名。

  • 因為AOT編譯可以放心大膽地進(jìn)行大量全程序的重負(fù)載優(yōu)化,所以無論是編譯時間還是空間占用都非??捎^。筆者在intel 9900K、64GB內(nèi)存的機(jī)器上,編譯了一個只引用了org.springframework.boot:spring-boot-starter-web的Helloworld類型的工程,大約耗費(fèi)了兩分鐘時間。

[com.example.exampleapplication:9839] (typeflow): 22,093.72 ms, 6.48 GB
[com.example.exampleapplication:9839] (objects): 34,528.09 ms, 6.48 GB[com.example.exampleapplication:9839] (features): 6,488.74 ms, 6.48 GB[com.example.exampleapplication:9839] analysis: 65,465.65 ms, 6.48 GB[com.example.exampleapplication:9839] (clinit): 2,135.25 ms, 6.48 GB[com.example.exampleapplication:9839] universe: 4,449.61 ms, 6.48 GB[com.example.exampleapplication:9839] (parse): 2,161.78 ms, 6.32 GB[com.example.exampleapplication:9839] (inline): 3,113.77 ms, 6.25 GB[com.example.exampleapplication:9839] (compile): 15,892.88 ms, 6.56 GB[com.example.exampleapplication:9839] compile: 25,044.34 ms, 6.56 GB[com.example.exampleapplication:9839] image: 6,580.71 ms, 6.63 GB[com.example.exampleapplication:9839] write: 1,362.73 ms, 6.63 GB[com.example.exampleapplication:9839] [total]: 120,410.26 ms, 6.63 GB[INFO][INFO] --- spring-boot-maven-plugin:2.3.0.M4:repackage (repackage) @ exampleapplication ---[INFO] Replacing main artifact with repackaged archive[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time: 02:08 min [INFO] Finished at: 2020-04-25T22:18:14+08:00[INFO] Final Memory: 38M/599M[INFO] ------------------------------------------------------------------------

 

效果評估

  • 筆者使用Graal VM編譯一個最簡單的Helloworld程序(就只在控制臺輸出個Helloworld,什么都不依賴),最終輸出的結(jié)果大約3.6MB,啟動時間能低至2ms左右。如果用這個程序去生成Docker鏡像(不基于任何基礎(chǔ)鏡像,即使用FROM scratch打包),產(chǎn)生的鏡像還不到3.8MB。而OpenJDK官方提供的Docker鏡像,即使是slim版,其大小也在200MB到300MB之間。

  • 使用Graal VM編譯一個簡單的Spring Boot Web應(yīng)用,僅導(dǎo)入Spring Boot的Web Starter的依賴的話,編譯結(jié)果有77MB,原始的Fat Jar包大約是16MB,這樣打包出來的Docker鏡像可以不依賴任何基礎(chǔ)鏡像,大小仍然是78MB左右(實際使用時最好至少也要基于alpine吧,不差那幾MB)。相比起空間上的收益,啟動時間上的改進(jìn)是更主要的,Graal VM的本地鏡像啟動時間比起基于虛擬機(jī)的啟動時間有著絕對的優(yōu)勢,一個普通Spring Boot的Web應(yīng)用啟動一般2、3秒之間,而本地鏡像只要100毫秒左右即可完成啟動,這確實有了數(shù)量級的差距。

  • 不過,必須客觀地說明一點,盡管Graal VM在啟動時間、空間占用、內(nèi)存消耗等容器化環(huán)境中比較看重的方面確實比HotSpot有明顯的改進(jìn),盡管Graal VM可以放心大膽地使用重負(fù)載的優(yōu)化手段,但如果是處于長時間運(yùn)行這個前提下,至少到目前為止,沒有任何跡象表明它能夠超越經(jīng)過充分預(yù)熱后的HotSpot。在延遲、吞吐量、可監(jiān)控性等方面,仍然是HotSpot占據(jù)較大優(yōu)勢,下圖引用了DEVOXX 2019中Graal VM團(tuán)隊自己給出的Graal VM與HotSpot JIT在各個方面的對比評估:

Graal VM:云原生時代的Java

Graal VM與HotSpot的對比

Graal VM團(tuán)隊同時也說了,Graal VM有望在2020年之內(nèi),在延遲和吞吐量這些關(guān)鍵指標(biāo)上追評HotSpot現(xiàn)在的表現(xiàn)。Graal VM畢竟是一個2018年才正式公布的新生事物,我們能看到它這兩三年間在可用性、易用性和性能上持續(xù)地改進(jìn),Graal VM有望成為Java在微服務(wù)時代里的最重要的基礎(chǔ)設(shè)施變革者,這項改進(jìn)的結(jié)果如何,甚至可能與Java的前途命運(yùn)息息相關(guān)。

作者簡介

周志明,騰訊云最具價值專家(TVP),Java技術(shù)、機(jī)器學(xué)習(xí)和企業(yè)級開發(fā)技術(shù)專家,現(xiàn)任遠(yuǎn)光軟件研究院院長,機(jī)器學(xué)習(xí)方向博士, 開源技術(shù)的積極倡導(dǎo)者和推動者,對計算機(jī)科學(xué)和相關(guān)的多個領(lǐng)域都有深刻的見解,尤其是人工智能、Java技術(shù)和敏捷開發(fā)等領(lǐng)域。曾受邀在InfoQ和IBMDeveloperWorks等網(wǎng)站撰寫技術(shù)專欄。

著有暢銷書多本。著有《智慧的疆界》、《深入理解Java虛擬機(jī)》、《深入理解OSGi》,翻譯了《Java虛擬機(jī)規(guī)范》等著作。其中《深入理解Java虛擬機(jī)》第1版出版于2011年,已經(jīng)出至第3版,累計印刷超過35次,銷量30萬冊;不僅銷量好,而且口碑更好,是中文計算機(jī)圖書領(lǐng)域公認(rèn)的、難得一見的佳作。

本文首發(fā)于騰訊TVP公眾號,技術(shù)原創(chuàng)及架構(gòu)實踐文章,歡迎通過公眾號菜單「聯(lián)系我們」進(jìn)行投稿。

高可用架構(gòu)

改變互聯(lián)網(wǎng)的構(gòu)建方式

分享到:
標(biāo)簽:Graal VM
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定