【JVM 深層系列】「云原生的 Java 虛擬機(jī)」對(duì)于 GraalVM 的重塑和探究

GraalVM 背景

新、舊編程語(yǔ)言的興起躁動(dòng),說(shuō)明必然有其需求動(dòng)力所在,譬如互聯(lián)網(wǎng)之于 JavaScript、人工智能之于 Python,微服務(wù)風(fēng)潮之于 Golang 等等。大家都清楚不太可能有哪門(mén)語(yǔ)言能在每一個(gè)領(lǐng)域都盡占優(yōu)勢(shì),Java 已是距離這個(gè)目標(biāo)最接近的選項(xiàng),但若“天下第一”還要百尺竿頭更進(jìn)一步的話(huà),似乎就只能忘掉 Java 語(yǔ)言本身,踏入無(wú)招勝有招的境界。

  • 更進(jìn)一步提升 JVM 上運(yùn)行的程序的性能

  • 通過(guò)預(yù)編譯(ahead-of-time)編譯 Java 程序?yàn)樵蓤?zhí)行程序

  • 多種編程語(yǔ)言混編在一個(gè)程序中(polyglot)

  • 類(lèi)似于 LLVM,GraalVM 也提供了方便的機(jī)制方便開(kāi)發(fā)新的編程語(yǔ)言

當(dāng)前痛點(diǎn)

在云原生時(shí)代,Java 程序是有很大的劣勢(shì)的,為什么這么說(shuō)呢?一般的 Java 應(yīng)用程序都要幾十兆的內(nèi)存,啟動(dòng)也不不快。

最流行的 SpringBoot/SpringCloud 微服務(wù)框架為例,啟動(dòng)一個(gè)已經(jīng)優(yōu)化好,很多 bean 需要 lazy load 的 application 至少需要 3-4 秒時(shí)間,內(nèi)存需要幾百兆,業(yè)務(wù)邏輯稍微復(fù)雜一點(diǎn)點(diǎn),沒(méi)有 1G 以上的內(nèi)存是很難滿(mǎn)足業(yè)務(wù)的需要呢?

那么在云原生時(shí)代,一個(gè)充滿(mǎn)黑科技的 JVM 介紹給大家,它能幫助我們讓 Java 程序的啟動(dòng)速度加快 100 倍,內(nèi)存只需要原來(lái)的五分之一,甚至更少。

Graalvm 的介紹

  • GraalVM 是 2018 年 Oracle 開(kāi)發(fā)的下一代 JVM 實(shí)現(xiàn),被官方稱(chēng)為“Universal VM”和“Polyglot VM”,這是一個(gè)在 HotSpot 虛擬機(jī)基礎(chǔ)上增強(qiáng)而成的跨語(yǔ)言全棧虛擬機(jī),可以作為“任何語(yǔ)言”的運(yùn)行平臺(tái)使用。

  • 這里“任何語(yǔ)言”包括了 Java、Scala、Groovy、Kotlin 等基于 Java 虛擬機(jī)之上的語(yǔ)言,還包括了 C、C++、Rust 等基于 LLVM 的語(yǔ)言,同時(shí)支持其他像 JavaScript、Ruby、Python 和 R 語(yǔ)言等等。

[圖片上傳失敗...(image-2d7c83-1636276908250)]

GraalVM 可以無(wú)額外開(kāi)銷(xiāo)地混合使用這些編程語(yǔ)言,支持不同語(yǔ)言中混用對(duì)方的接口和對(duì)象,也能夠支持這些語(yǔ)言使用已經(jīng)編寫(xiě)好的本地庫(kù)文件。

它的口號(hào)“Run Programs Faster Anywhere”就能感覺(jué)到一顆蓬勃的野心

Graalvm 性能的對(duì)比

GraalVM 的性能真的不錯(cuò)。以 JDK 8 為例

  • OpenJDK

  • Oracle JDK

其中 OpenJDK 是通過(guò)“GPL v2 with CE”協(xié)議開(kāi)源的,可以免費(fèi)商用的。

之前在用 Apache Spark 測(cè)試性能時(shí), 對(duì)比一下兩者性能, 稍微數(shù)據(jù)量大點(diǎn)的查詢(xún),會(huì)發(fā)現(xiàn) Oracle JDK 一般都會(huì)比 OpenJDK 快 30%以上。

  • 而 GraalVM 分為社區(qū)版和商業(yè)版,其中 GraalVM 的社區(qū)版也是采用了和 OpenJDK 一樣的“GPL v2 with CE”協(xié)議開(kāi)源的。

  • 對(duì)于 GraalVM 的社區(qū)版,非常驚喜的發(fā)現(xiàn)其比 Oracle JDK 也會(huì)快 10%以上。

  • 沒(méi)有試過(guò) GraalVM 的商業(yè)版, 官方報(bào)道,其商業(yè)版比社區(qū)版提升的性能更多

Graalvm 主要特性

  • 高性能的現(xiàn)代 Java

  • 占用資源少,啟動(dòng)速度快

  • JavaScript,Java, Ruby 以及 R 混合編程

  • 在 JVM 上運(yùn)行原生語(yǔ)言

  • 跨語(yǔ)言工具

  • JVM 應(yīng)用擴(kuò)展

  • 原生應(yīng)用擴(kuò)展

  • 本地 Java 庫(kù)

  • 數(shù)據(jù)庫(kù)支持多語(yǔ)言

  • 創(chuàng)建自己的語(yǔ)言

Graalvm 工作原理

GraalVM 的基本工作原理是將這些語(yǔ)言的源代碼(例如,JavaScript)或源代碼編譯后的中間格式(例如,LLVM 字節(jié)碼、Class 字節(jié)碼)通過(guò)解釋器轉(zhuǎn)換為能被 GraalVM 接受的中間表示(Intermediate Representation,IR),譬如設(shè)計(jì)一個(gè)解釋器專(zhuān)門(mén)對(duì) LLVM 輸出的字節(jié)碼進(jìn)行轉(zhuǎn)換來(lái)支持 C 和 C++語(yǔ)言,這個(gè)過(guò)程稱(chēng)為“程序特化”(Specialized,也常稱(chēng)為 Partial Evaluation)。

GraalVM 提供了 Truffle 工具集來(lái)快速構(gòu)建面向一種新語(yǔ)言的解釋器,并用它構(gòu)建了一個(gè)稱(chēng)為 Sulong 的高性能 LLVM 字節(jié)碼解釋器。

從某個(gè)角度來(lái)看,GraalVM 才是真正意義上與物理計(jì)算機(jī)相對(duì)應(yīng)的高級(jí)語(yǔ)言虛擬機(jī),因?yàn)樗c物理硬件的指令集一樣,做到了只與機(jī)器特性相關(guān)而不與某種高級(jí)語(yǔ)言特性相關(guān)。

Graalvm 的高等優(yōu)化能力

Oracle Labs 的研究總監(jiān) Thomas Wuerthinger 在接受采訪時(shí)談到:“隨著 GraalVM1.0 的發(fā)布,已經(jīng)證明了擁有高性能的多語(yǔ)言虛擬機(jī)是可能的,并且實(shí)現(xiàn)這個(gè)目標(biāo)的最佳方式不是通過(guò)類(lèi)似 Java 虛擬機(jī)和微軟 CLR 那樣帶有語(yǔ)言特性的字節(jié)碼”。

本來(lái)就不以速度見(jiàn)長(zhǎng)的語(yǔ)言運(yùn)行環(huán)境,由于 GraalVM 本身能夠?qū)斎氲闹虚g表示進(jìn)行自動(dòng)優(yōu)化,在運(yùn)行時(shí)還能進(jìn)行即時(shí)編譯優(yōu)化,往往使用 GraalVM 實(shí)現(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 語(yǔ)言等等。

Graalvm 與 Hotspot 的對(duì)比

GraalVM 本來(lái)就是在 HotSpot 基礎(chǔ)上誕生的,天生就可作為一套完整的符合 Java SE8 標(biāo)準(zhǔn) Java 虛擬機(jī)來(lái)使用。

它和標(biāo)準(zhǔn)的 HotSpot 差異主要在即時(shí)編譯器上,其執(zhí)行效率、編譯質(zhì)量目前與標(biāo)準(zhǔn)版的 HotSpot 相比也是互有勝負(fù)。

Oracle Labs 和美國(guó)大學(xué)里面的研究院所做的最新即時(shí)編譯技術(shù)的研究全部都遷移至基于 GraalVM 之上進(jìn)行了,其發(fā)展?jié)摿α钊似诖?/p>

如果 Java 語(yǔ)言或者 HotSpot 虛擬機(jī)真的有被取代的一天,那從現(xiàn)在看來(lái) GraalVM 是希望最大的一個(gè)候選項(xiàng),這場(chǎng)革命很可能會(huì)在 Java 使用者沒(méi)有明顯感覺(jué)的情況下悄然而來(lái),Java 世界所有的軟件生態(tài)都沒(méi)有發(fā)生絲毫變化,但天下第一的位置已經(jīng)悄然更迭。

Graalvm 即時(shí)編譯器

自 JDK 10 起,HotSpot 中又加入了一個(gè)全新的即時(shí)編譯器:Graal 編譯器,看名字就可以聯(lián)想到它是來(lái)自于 Graal VM。

C1/C2 即時(shí)編譯器

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

HotSpot 虛擬機(jī)中包含有兩個(gè)即時(shí)編譯器:

  • 編譯時(shí)間較短但輸出代碼優(yōu)化程度較低的客戶(hù)端編譯器(簡(jiǎn)稱(chēng)為 C1)

  • 編譯耗時(shí)長(zhǎng)但輸出代碼優(yōu)化質(zhì)量也更高的服務(wù)端編譯器(簡(jiǎn)稱(chēng)為 C2)

通常它們會(huì)在分層編譯機(jī)制下與解釋器互相配合來(lái)共同構(gòu)成 HotSpot 虛擬機(jī)的執(zhí)行子系統(tǒng)的。

C2 即時(shí)編譯器

Graal 編譯器是作為 C2 編譯器替代者的角色登場(chǎng)的。C2 的歷史已經(jīng)非常長(zhǎng)了,可以追溯到 Cliff Click 大神讀博士期間的作品,這個(gè)由 C++寫(xiě)成的編譯器盡管目前依然效果拔群,但已經(jīng)復(fù)雜到連 Cliff Click 本人都不愿意繼續(xù)維護(hù)的程度。

Graal 編譯器

而 Graal 編譯器本身就是由 Java 語(yǔ)言寫(xiě)成,實(shí)現(xiàn)時(shí)又刻意與 C2 采用了同一種名為“Sea-of-Nodes”的高級(jí)中間表示(High IR)形式,使其能夠更容易借鑒 C2 的優(yōu)點(diǎn)。

Graal 編譯器比 C2 編譯器晚了足足二十年面世,有著極其充沛的后發(fā)優(yōu)勢(shì),在保持能輸出相近質(zhì)量的編譯代碼的同時(shí),開(kāi)發(fā)效率和擴(kuò)展性上都要顯著優(yōu)于 C2 編譯器,這決定了 C2 編譯器中優(yōu)秀的代碼優(yōu)化技術(shù)可以輕易地移植到 Graal 編譯器上,但是反過(guò)來(lái) Graal 編譯器中行之有效的優(yōu)化在 C2 編譯器里實(shí)現(xiàn)起來(lái)則異常艱難。

這種情況下,Graal 的編譯效果短短幾年間迅速追平了 C2,甚至某些測(cè)試項(xiàng)中開(kāi)始逐漸反超 C2 編譯器。

Graal 能夠做比 C2 更加復(fù)雜的優(yōu)化:

  • “部分逃逸分析”(Partial Escape Analysis)

  • 比 C2 更容易使用“激進(jìn)預(yù)測(cè)性?xún)?yōu)化”(Aggressive Speculative Optimization)的策略

  • 支持自定義的預(yù)測(cè)性假設(shè)

未來(lái)可期

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

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

編譯為原生執(zhí)行程序

編譯為原生程序有一定的假設(shè)條件,比如:

  • 盡量少的 JNI 調(diào)用

  • 盡量少的使用反射

  • 盡量少的 class loader 隔離等

當(dāng)沒(méi)有用這些復(fù)雜功能的時(shí)候,很容易可以使用 GraalVM 提供的 native image 編譯 Jar 為可執(zhí)行程序。

當(dāng)然即使當(dāng)程序使用了 JNI、反射時(shí),也沒(méi)關(guān)系,我們可以使用一些配置文件告訴 GraalVM 單獨(dú)處理這些信息,比如:

  • 通過(guò)參數(shù) -H:JNIConfigurationFiles 告訴 JNI 相關(guān)配置 JSON 文件

  • 通過(guò)參數(shù) -H:ReflectionConfigurationFiles 告訴反射相關(guān)配置 JSON 文件

稍微會(huì)復(fù)雜一些,但是只要有足夠的耐心,理論上也是可以編譯成功的!

不過(guò)我們可以使用一些原生支持 GraalVM native image 的框架, 比如:Quarkus。

GraalVM 的原生編譯非常適合微服務(wù)和 Serverless

當(dāng)可以把 Java 程序也編譯為原生的可執(zhí)行程序后 (目前 GraalVM 已經(jīng)支持編譯為 Windows, MacOS, Linux 上的原生程序),最主要的兩個(gè)變化:

  • 啟動(dòng)時(shí)間變短了,之前啟動(dòng)一個(gè)有“依賴(lài)注入”的 Java 程序,可能啟動(dòng)時(shí)間要 2 秒以上。如果 Java 程序是要長(zhǎng)期運(yùn)行的,那啟動(dòng)時(shí)間稍慢一點(diǎn)是沒(méi)問(wèn)題的,但是對(duì)于 Serverless 應(yīng)用,這就變?yōu)槔鋯?dòng)(cold start)了,影響比較大。

  • 程序運(yùn)行的內(nèi)存需求變小了,之前啟動(dòng)一個(gè) Java 程序,控制的好的話(huà)(heap 設(shè)置的比較?。?,也要 100M 以上的內(nèi)存,但是編譯為原生程序后,只需要 4M 內(nèi)存就可以了。 這樣同樣的一臺(tái)機(jī)器就可以啟動(dòng)非常多的進(jìn)程,適合簡(jiǎn)單的微服務(wù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容