Spring 官宣:換掉 JVM!
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)
Spring 團(tuán)隊(duì)日前發(fā)布了 Spring Native Beta 版。通過 Spring Native,Spring 應(yīng)用將有機(jī)會(huì)與?GraalVM?原生鏡像的方式運(yùn)行。為了更好地支持原生運(yùn)行,Spring Native 提供了 Maven 和 Gradle 插件,并且提供了優(yōu)化原生配置的注解。
Spring 發(fā)布了 Spring Native 的 beta 版本,并在http://start.spring.io上運(yùn)行它
實(shí)際上,這意味著自Spring成立以來,除了Spring支持的常規(guī)Java虛擬機(jī)之外,我們還將添加Beta支持,以使用GraalVM將Spring應(yīng)用程序編譯到本機(jī)映像中,從而提供一種部署Spring應(yīng)用程序的新方法。支持Java和Kotlin。
這些本機(jī)Spring應(yīng)用程序可以部署為獨(dú)立的可執(zhí)行文件(無需安裝JVM),并提供有趣的特性,包括幾乎即時(shí)啟動(dòng)(通常<100ms),即時(shí)峰值性能和較低的內(nèi)存消耗,但所需的構(gòu)建時(shí)間和運(yùn)行時(shí)優(yōu)化次數(shù)少于JVM。

使用簡(jiǎn)單mvn spring-boot:build-image或gradle bootBuildImage命令,您可以生成一個(gè)優(yōu)化的容器映像,該映像將包含一個(gè)最小的OS層和一個(gè)小的本機(jī)可執(zhí)行文件,該映像僅隨附JDK,Spring以及您在應(yīng)用程序中使用的依賴項(xiàng)中的必需位。
請(qǐng)參閱下面的示例,其中包含50MB可執(zhí)行文件的最小容器映像,其中包含Spring Boot,Spring MVC,Jackson,Tomcat,JDK和應(yīng)用程序。

這種原生方式,在很多場(chǎng)景下都會(huì)對(duì) Spring 應(yīng)用產(chǎn)生價(jià)值:
具有Spring Cloud功能的無服務(wù)器 以更便宜和更可持續(xù)的方式托管Spring微服務(wù) 非常適合VMware Tanzu等Kubernetes平臺(tái) 想要?jiǎng)?chuàng)建最佳的容器映像來打包您的Spring應(yīng)用程序和服務(wù)
在使用場(chǎng)景上,比如 Piotr Mińkowski 提供了一個(gè)非常棒的指南,介紹了如何在 Knative 上使用 Spring Boot 和 GraalVM 構(gòu)建原生微服務(wù)。
其實(shí)這個(gè)技術(shù)在阿里,他們也探索了兩種不同維度上的冷啟動(dòng)加速技術(shù),經(jīng)過雙 11 大促的檢驗(yàn)都取得了良好的效果:AppCDS 技術(shù)在傳統(tǒng) Java 環(huán)境的維度上,利用類數(shù)據(jù)共享特性改進(jìn)啟動(dòng)速度和減少內(nèi)存開銷;靜態(tài)編譯技術(shù)則在更為激進(jìn)的維度上,將 Java 程序提前編譯為二進(jìn)制機(jī)器碼,實(shí)現(xiàn)以 Native Code 的速度啟動(dòng) Java 程序,帶來最多兩個(gè)數(shù)量級(jí)的啟動(dòng)性能提升。
阿里JVM 團(tuán)隊(duì)技術(shù)專家林子熠博士在最新出版的《GraalVM與Java靜態(tài)編譯:原理與應(yīng)用》一書中,揭秘Oracle GraalVM中Java靜態(tài)編譯技術(shù)的特性、實(shí)現(xiàn)原理、應(yīng)用與調(diào)試技巧,以突破Java“冷啟動(dòng)”桎梏,實(shí)現(xiàn)啟動(dòng)性能“質(zhì)”的飛躍。
作者介紹?:林子熠 博士
《GraalVM與Java靜態(tài)編譯:原理與應(yīng)用》作者
上海交通大學(xué)軟件工程博士
美國(guó)伊利諾伊大學(xué)香檳分校(UIUC)訪問學(xué)者
中國(guó)計(jì)算機(jī)學(xué)會(huì)系統(tǒng)軟件專委會(huì)委員
曾任華為方舟編譯器前端核心
現(xiàn)就職于阿里巴巴集團(tuán)JVM團(tuán)隊(duì),負(fù)責(zé)GraalVM的Java靜態(tài)編譯和靜態(tài)分析技術(shù)在阿里的落地應(yīng)用
Graal開源社區(qū)主要貢獻(xiàn):
序列化、動(dòng)態(tài)類加載、獨(dú)立靜態(tài)分析工具
以下是來自林老師QCon北京大會(huì)的分享,以期幫你實(shí)現(xiàn)云原生場(chǎng)景下 Java 快速冷啟動(dòng)(下文以林子熠老師第一人稱敘述)
Java 誕生至今的 25 年里,憑借其峰值性能高、語言功能強(qiáng)、生態(tài)支持好等特點(diǎn)贏得了語言市場(chǎng)的霸主地位。但 Java 冷啟動(dòng)開銷大,而云原生時(shí)代下的應(yīng)用程序短小,啟動(dòng)頻繁,冷啟動(dòng)問題的解決機(jī)不容發(fā)。
下圖為典型 Java 應(yīng)用的生命周期:

如圖,Java 應(yīng)用生命周期分為 5 個(gè)階段:VM 初始化階段、APP 初始化階段、APP 初活躍階段、APP 穩(wěn)定執(zhí)行期、結(jié)束階段。
VM 初始化(圖中紅色)和 Class loading(圖中藍(lán)色)的開銷為冷啟動(dòng)的根因。阿里巴巴實(shí)現(xiàn)了兩類改造:一類為改良型技術(shù),調(diào)整優(yōu)化現(xiàn)有 Java 的框架和運(yùn)行模型,另外一類為革新型的技術(shù),擺脫原有 Java 框架另起爐灶。
改良型技術(shù)中,阿里巴巴主要實(shí)現(xiàn)了基于傳統(tǒng) CDS(Class Data Sharing)的 EagerAppCDS。傳統(tǒng) CDS 包括 mark、Klass*、fields 三部分,如下圖所示:
Klass* 指針指向內(nèi)存中 class 實(shí)例 InstanceKlass,該實(shí)例為 bytecode Class Loading 解析后生成的,但在多次解析時(shí)內(nèi)容均不變,因此可固定存儲(chǔ)磁盤文件 Shared Archive 中,下次運(yùn)行時(shí)從文件中可省略解析直接讀取,實(shí)現(xiàn)提速。
面對(duì) system class,CDS 可根據(jù) name 快速匹配,但面對(duì) customized class 時(shí),JVM 無法辨認(rèn) customized class loader 的身份,因此需在 classpath 上掃描 jar 包,根據(jù) name、crc 校驗(yàn)后方可完成匹配。jar 包即為 libs 包,包含大量 I/O 操作,開銷大。EagerAppCDS 用 identity 固定了customizedclass loader 的名字,可直接通過 identity+name 匹配找到所需 class。
下圖為 EagerAppCDS 在阿里巴巴內(nèi)部實(shí)踐的脫敏數(shù)據(jù),如圖所示性能提升效果從 12%~95% 不等。

EagerAppCDS 雖未開源但已在阿里云 SAE(Serverless 微服務(wù) PaaS 平臺(tái))上線。線上可公開實(shí)測(cè)數(shù)據(jù)中應(yīng)用啟動(dòng)耗時(shí)降低 5%~45%,提升效果與啟動(dòng)時(shí)加載類數(shù)量成正比。

除此之外,我們還實(shí)現(xiàn)了以下改進(jìn)型技術(shù):
JWarmup:共享預(yù)熱后的 code cache,減小 JIT 開銷;
PGO AOT:提前準(zhǔn)備 runtime 信息,指導(dǎo)改進(jìn) AOT 代碼質(zhì)量;
Class Preinit:類預(yù)先初始化,降低運(yùn)行時(shí)初始化類的開銷。
革新型技術(shù)中,阿里巴巴采用了基于 Graal VM 的靜態(tài)編譯技術(shù)。Graal VM 為 Oracle 主導(dǎo)的基于 Java 的開源高性能多語言平臺(tái):C++、Kotlin、python 等多種語言可通過 Truffle 框架運(yùn)行在 GraalVM 上,Java 和其他 JVM 語言(如 Groovy、Kotlin 和 Scala 等)編譯成 bytecode 后可直接運(yùn)行。
Substrate VM(SVM)為 Graal VM 的靜態(tài)編譯組件,可將 Java 程序靜態(tài)編譯為可執(zhí)行文件或共享庫文件 Native Image,實(shí)現(xiàn)直接編譯 Java 代碼。
Java 最初依靠解釋器實(shí)現(xiàn)無需編譯實(shí)時(shí)執(zhí)行;該解釋器性能較差,因此引入了 JIT(Just In Time)實(shí)時(shí)編譯技術(shù),將高熱度函數(shù)送到編譯器中編譯;為解決編譯器開銷大的問題,引入了 AOT(Ahead Of Time)編譯,提前編譯部分代碼;AOT 缺乏 runtime 數(shù)據(jù),運(yùn)行后即喪失轉(zhuǎn)為 JIT 的機(jī)會(huì),運(yùn)行速度慢;靜態(tài)編譯技術(shù)將 AOT 擴(kuò)大,徹底摒棄 JVM,由 SVM 提供運(yùn)行環(huán)境。
傳統(tǒng) Java 執(zhí)行模型如下圖所示:Application(應(yīng)用本身)在 libs 的支持下運(yùn)行在 JDK 上在 JVM 中執(zhí)行。
靜態(tài)編譯在 Graal Compiler 編譯器中編譯 Application、libs、JDK,同時(shí)編譯 Substrate VM Runtime,獲得 Native Image。Native Image 包含 code(編譯后的代碼)和 Image heap(存儲(chǔ)數(shù)據(jù))兩部分。Image heap 為運(yùn)行時(shí) heap 的起點(diǎn),直接讀取 Image heap 可以提高運(yùn)行時(shí)的性能。
靜態(tài)編譯必須遵循封閉性原則 (the closed-world assumption),即所有運(yùn)行時(shí)信息均需在編譯時(shí)可見。該原則帶來兩個(gè)基本問題:如何確定封閉的邊界?如何處理 Java 的動(dòng)態(tài)特性?
Java bytecode 編譯為 Native code 時(shí),代碼抽象性降低體積增大,如若編譯所有代碼,Native Image 體積將過于龐大,因此需確定封閉邊界。SVM 通過靜態(tài)分析上實(shí)現(xiàn)了從給定入口開始確定程序可達(dá)范圍的功能。
該技術(shù)應(yīng)用廣泛,例如 main 函數(shù)調(diào)用 Virtue call 必須先明確其 type,type 和 Virtue call 有時(shí)可唯一綁定,但通常不能唯一綁定。此時(shí)使用靜態(tài)分析技術(shù),可明確 Virtue call type 的可能范圍,實(shí)現(xiàn)封閉。

受靜態(tài)分析本身的特性和能力所限,靜態(tài)分析得到的可達(dá)代碼集合(藍(lán)色)略大于實(shí)際執(zhí)行代碼集合(綠色)。靜態(tài)分析精度越高、冗余越少、image 越小。
靜態(tài)分析無法分析出 Java 的許多動(dòng)態(tài)特性運(yùn)行時(shí)的行為,如反射、動(dòng)態(tài)代理、JNI、序列化(阿里巴巴貢獻(xiàn),從 21.0 開始支持)、動(dòng)態(tài)類加載(阿里巴巴貢獻(xiàn),patch 已經(jīng)通過評(píng)審)等。此時(shí)需提前獲取所需信息,方可封閉此類動(dòng)態(tài)特性的觸達(dá)范圍——即需基于配置進(jìn)行動(dòng)態(tài)特性支持。
以反射為例。SVM 提供了 native-image-agent,可記錄 APP 運(yùn)行時(shí)所有的反射。編譯時(shí)只需解析配置文件,即可注冊(cè)反射目標(biāo),擴(kuò)大編譯范圍;同時(shí)獲取反射信息后可放入 ReflectionData 緩存中,將反射調(diào)用替換為直接調(diào)用。運(yùn)行時(shí)如遇反射可查找 ReflectionData,獲取目標(biāo)值,通過 Method.invoke 直接調(diào)用目標(biāo)函數(shù)。
下圖為通過靜態(tài)編譯和傳統(tǒng) Java 兩種方式,分別用反射調(diào)用空函數(shù) 30 次性能對(duì)比測(cè)試結(jié)果:
由于峰值過高,該圖進(jìn)行了對(duì)數(shù)修正。傳統(tǒng) Java 編譯空函數(shù)耗時(shí)(深藍(lán)色)為 3000ns,峰值由于反射開銷為 4000ns,靜態(tài)編譯后(深藍(lán)色)穩(wěn)定在 150ns 內(nèi)。
SVM 的靜態(tài)編譯實(shí)現(xiàn)的編譯優(yōu)化包括標(biāo)準(zhǔn)優(yōu)化如:Method inlining, constant folding and arithmetic optimization, loop optimization, partial escape analysis 等。
此外還有因?yàn)殪o態(tài)分析而引入的新的編譯優(yōu)化,例如未被標(biāo)為 final 的 field 通過靜態(tài)分析發(fā)現(xiàn)只讀不寫,即可當(dāng)作常量處理,做常量折疊等優(yōu)化;又如,靜態(tài)分析出某虛函數(shù) type 唯一綁定,即可優(yōu)化為直接調(diào)用,進(jìn)而實(shí)現(xiàn) inline;再如,消除部分編譯時(shí)已知變量類型狀態(tài)的類型檢查和空指針檢查。
靜態(tài)編譯由于所有的類均已被編譯因此只有一個(gè)類加載器,實(shí)際只執(zhí)行類查找功能。
傳統(tǒng) Java 一邊檢查異常一邊運(yùn)行,如遇異常直接處理即可。SVM 考慮到在不同平臺(tái)兼容性,異常處理采用非信號(hào)處理機(jī)制:檢測(cè)無錯(cuò)方可正常運(yùn)行。該檢測(cè)對(duì)性能影響小。
此外,靜態(tài)編譯的 GC 為 Oracle 開源版本中的單線程 stop-and-copy 順序 GC,性能一般。
下圖為 Graal VM 官方的實(shí)驗(yàn)數(shù)據(jù):

如上圖所示,在只執(zhí)行 Hello world 程序時(shí),Native Image 性能次于 C,與 Go 相當(dāng),遠(yuǎn)快于傳統(tǒng) JDK;內(nèi)存使用次于 C,只有 Go 的一半,遠(yuǎn)低于傳統(tǒng) JDK,具有高性能低內(nèi)存占用的優(yōu)點(diǎn)。圖中紅色數(shù)據(jù)為受測(cè)語言數(shù)據(jù)除以 Native Image 數(shù)據(jù)所得比值。
Javac 為 Java 編寫的編譯器:可以在 Java 程序中來調(diào)用 API 編譯,也可用 stand alone 工具編譯。通過 API 調(diào)用,實(shí)際上已完成 VM 啟動(dòng),因此兩者對(duì)比可觀察冷啟動(dòng)帶來的性能差異。

通過 API 調(diào)用 Javac 耗時(shí) 250ms,使用 Native Image 后耗時(shí)達(dá)到 35ms,實(shí)現(xiàn)了 1 個(gè)數(shù)量級(jí)的飛躍。
Javac 中使用的反射、Serverless 較少,其他項(xiàng)目靜態(tài)分析性能提升效果更加顯著。下圖為基于 spring boot 的應(yīng)用 greeting-service 部署在阿里云函數(shù)計(jì)算平臺(tái)上的數(shù)據(jù)。greeting-service 收到請(qǐng)求會(huì)返回“ hallo”,功能簡(jiǎn)單但需要 spring boot 全流程支持。

如圖所示,Native Image 靜態(tài)編譯(橙色)相比傳統(tǒng) Java(藍(lán)色):內(nèi)存占用從 128MB 降至 21MB;實(shí)際第一次調(diào)用耗時(shí)從 454 ms 降至 4.27 ms,提升了兩個(gè)數(shù)量級(jí);阿里云服務(wù)計(jì)費(fèi)從 500ms 降至 100ms,事半功倍。

靜態(tài)編譯的局限性如上表所示:
為實(shí)現(xiàn)封閉性,反射、動(dòng)態(tài)代理、JNI、序列化、動(dòng)態(tài)類加載均需要通過配置支持;
不支持 InvokeDynamic(開發(fā)人員使用)、Method Handles(開發(fā)人員使用)、Security Manager、多 classloader、Finalizers、過時(shí) Thread 函數(shù)(如 Thread.stop())等;
Java 程序被靜態(tài)編譯后不再保留 bytecode,因此存在監(jiān)控、調(diào)試方面的問題:不支持 JVMTI、JMX、agent,只能使用 GDB 調(diào)試,無法通過 Eclipse IDE、IntelliJ IDEA 等調(diào)試。
GraalVM 靜態(tài)編譯目前生態(tài)如下:
阿里云:通過阿里云函數(shù)計(jì)算平臺(tái)進(jìn)行支持部署 serverless Native Image 應(yīng)用,通過 Apache RocketMQ 為 C++ 客戶端提供使用靜態(tài)編譯的 Java 共享庫;
Spring 社區(qū):發(fā)布了針對(duì)于靜態(tài)編譯 Spring-Native beta 版本,完全支持 Spring 的運(yùn)算;
MICRONAUT:實(shí)現(xiàn)了支持 Native Image 的去反射微服務(wù)框架;
Facebook & Twitter:均在生產(chǎn)環(huán)境下使用 Graal 編譯器代替 C2 編譯器。
總之,在 Serverless 場(chǎng)景下 Java 的冷啟動(dòng)問題與應(yīng)用對(duì)快速響應(yīng)、實(shí)時(shí)擴(kuò)展的需求形成突出矛盾。阿里巴巴一方面在現(xiàn)有技術(shù)上不斷改進(jìn),最終形成突破:EagerAppCDS 提升最多 45% 的啟動(dòng)速度;另一方面積極參與開源社區(qū)探索創(chuàng)新型的前沿技術(shù),打磨成熟用于實(shí)踐:GraalVM 靜態(tài)編譯技術(shù)最多提升百倍啟動(dòng)速度。但 GraalVM 存在兼容性和改造成本的問題,適合新項(xiàng)目。
來源 | QCon全球軟件開發(fā)大會(huì)
嘉賓 | 林子熠? ? ?
整理 | 李慧文
????
往 期 推 薦
1、拖動(dòng)文件就能觸發(fā)7-Zip安全漏洞,波及所有版本
3、一次 SQL 查詢優(yōu)化原理分析:900W+ 數(shù)據(jù),從 17s 到 300ms
4、Redis數(shù)據(jù)結(jié)構(gòu)為什么既省內(nèi)存又高效?
點(diǎn)分享
點(diǎn)收藏
點(diǎn)點(diǎn)贊
點(diǎn)在看





