美團(tuán)架構(gòu)師探秘Java生態(tài)系統(tǒng),介紹JDK、JVM、JEP

OpenJDK

OpenJDK原是Sun MicroSystems公司(下面簡(jiǎn)稱Sun公司)為Java平臺(tái)構(gòu)建的Java開(kāi)發(fā)環(huán)境,于2009年4月15日由Sun公司正式發(fā)布。后來(lái)Oracle公司在2010年收購(gòu)Sun公司,接管了這項(xiàng)工作。

隨著OpenJDK的發(fā)布,越來(lái)越多的公司和組織都基于OpenJDK深度定制了一些獨(dú)具特色的JDK分支,為用戶提供更多選擇。例如,國(guó)內(nèi)廠商阿里巴巴的Dragonwell支持JWarmup,可以讓代碼在灰度環(huán)境預(yù)熱編譯后供生產(chǎn)環(huán)境直接使用;騰訊的Kona 8將高版本的JFR和CDS移植到JDK 8上;龍芯JDK支持包含JIT的MIPS架構(gòu),而非Zero的解釋器版本;

國(guó)外廠商Amazon、Azul、Google、Microsoft、Red Hat、Twitter等都有維護(hù)自用或者開(kāi)源的JDK分支。

回到OpenJDK本身。OpenJDK包含很多子項(xiàng)目,它們大都是為了實(shí)現(xiàn)某一較大的特性而立項(xiàng),關(guān)注它們可以了解Java社區(qū)的最新動(dòng)向和研究方向。一些重要和有趣的子項(xiàng)目如下所示。

1)Amber:探索與孵化一些小的、面向生產(chǎn)力提升的Java語(yǔ)言特性。Amber項(xiàng)目的貢獻(xiàn)包括模式匹配、Switch表達(dá)式、文本塊、局部變量類型推導(dǎo)等語(yǔ)言特性。

2)Coin:決定哪些小的語(yǔ)言改變會(huì)添加進(jìn)JDK7。常用的鉆石形式的泛型類型推導(dǎo)語(yǔ)法以及try-with-resource語(yǔ)句都來(lái)自Coin項(xiàng)目。

3)Graal:Graal最初是基于JVMCI的編譯器,后面進(jìn)一步發(fā)展出Graal VM,旨在開(kāi)發(fā)一個(gè)通用虛擬機(jī)平臺(tái),允許JavaScript、Python、Ruby、R、JVM等語(yǔ)言運(yùn)行在同一個(gè)虛擬機(jī)上而不需要修改應(yīng)用自身的代碼。

4)Jigsaw:Jigsaw孵化了Java 9的模塊系統(tǒng)。

5)Kulla:實(shí)現(xiàn)一個(gè)交互式REPL工具,即JEP 222的JShell工具。

6)Loom:探索與孵化JVM特性及API,并基于此構(gòu)建易用、高吞吐量的輕量級(jí)并發(fā)與編程模型。目前Loom的研究方向包括協(xié)程、Continuation、尾遞歸消除。

7)Panama:溝通JVM和機(jī)器代碼,研究方向有Vector API和新一代JNI。

8)Shenandoah:擁有極低暫停時(shí)間的垃圾回收器。相較并發(fā)標(biāo)記的CMS和G1,Shenandoah增加了并發(fā)壓縮功能。

9)Sumatra:讓Java程序享受GPU、APU等異構(gòu)芯片帶來(lái)的好處。

目前關(guān)注于讓GPU在HotSpot VM代碼生成、運(yùn)行時(shí)支持和垃圾回收上發(fā)揮作用。

10)Tsan:為Java提供Thread Sanitizer檢查工具,可以檢查Java和JNI代碼中潛在的數(shù)據(jù)競(jìng)爭(zhēng)。

11)Valhalla:探索與孵化JVM及Java的語(yǔ)言特性,主要貢獻(xiàn)有備受矚目的值類型(Value Type)、嵌套權(quán)限訪問(wèn)控制(Nest-based AccessControl),以及對(duì)基本類型作為模板參數(shù)的泛型支持。

12)ZGC:低延時(shí)、高伸縮的垃圾回收器。它的目標(biāo)是使暫停時(shí)間不超過(guò)10ms,且不會(huì)隨著堆變大或者存活對(duì)象變多而變長(zhǎng),同時(shí)可以接收(包括但不限于)小至幾百兆,大至數(shù)十T的堆。ZGC的關(guān)鍵字包括并發(fā)、Region、壓縮、支持NUMA、使用著色指針、使用讀屏障。

JEP

JEP(Java Enhancement Proposal)即Java改進(jìn)提案。所謂提案是指社區(qū)在某方面的努力,比如在需要一次較大的代碼變更,或者某項(xiàng)工作的目標(biāo)、進(jìn)展、結(jié)果值得廣泛討論時(shí),就可以起草書(shū)面的、正式的JEP到OpenJDK社區(qū)。每個(gè)JEP都有一個(gè)編號(hào),為了方便討論,通常使用JEPID代替某個(gè)改進(jìn)提案。

JEP之于Java就像PEP之于Python、RFC之于Rust,它代表了Java社區(qū)最新的工作動(dòng)向和未來(lái)的研究方向。在較大的Java/JVM特性實(shí)現(xiàn)前通常都有JEP,它是Java社區(qū)成員和領(lǐng)導(dǎo)者多輪討論的結(jié)果。JEP描述了該工作的動(dòng)機(jī)、目標(biāo)、詳細(xì)細(xì)節(jié)、風(fēng)險(xiǎn)和影響等,通過(guò)閱讀JEP(如果有的話),可以更好地了解Java/JVM某個(gè)特性。下面摘錄了一些較新的JEP,注意,處于“草案”和“候選”狀態(tài)的JEP不能保證最終會(huì)被加入JDK發(fā)行版。

1)JEP 386(候選):將OpenJDK移植到Alpine Linux/x64。Alpine是一個(gè)極簡(jiǎn)的Linux發(fā)行版,作為Docker基礎(chǔ)鏡像,它的大小不到6MB,被廣泛用于程序的云部署,但是Alpine使用musl作為C語(yǔ)言運(yùn)行時(shí)庫(kù),與廣泛使用的glibc有些出入,而JEP 386可以很好地解決這個(gè)問(wèn)題。

2)JEP 378:Java文本塊。文本塊即多行的字符串字面值,其功能類似于其他編程語(yǔ)言的raw字符串功能,不需要為大多數(shù)特殊字符轉(zhuǎn)義。將于JDK 15發(fā)布。

3)JEP 337(候選):讓高性能計(jì)算和云端程序充分利用網(wǎng)絡(luò)硬件并不容易,當(dāng)前JDK的網(wǎng)絡(luò)API使用操作系統(tǒng)內(nèi)核的socket協(xié)議,在數(shù)據(jù)傳輸時(shí)涉及內(nèi)核態(tài)和用戶態(tài)的多次切換,會(huì)影響內(nèi)存帶寬和CPU周期。

為了改善這種情況,Java準(zhǔn)備擬定實(shí)現(xiàn)rsocket協(xié)議,允許網(wǎng)絡(luò)API訪問(wèn)遠(yuǎn)端內(nèi)存(RDMA),提高吞吐量并降低網(wǎng)絡(luò)延時(shí)。

4)JEP 369:使用GitHub作為OpenJDK的Git倉(cāng)庫(kù)。

5)JEP 384:提供Java記錄支持。很多人都說(shuō)Java不靈活,比如equals/hashCode等寫起來(lái)太長(zhǎng)了。在Spring或者一些RPC框架中,有時(shí)候僅僅想寫一個(gè)單純用作數(shù)據(jù)傳輸?shù)念?,也少不了要寫一大堆重?fù)方法和近乎刻板的getter/setter、toString、hashCode等方法,而且容易出錯(cuò)。

盡管IDE或者框架可以自動(dòng)生成這些類,但是它們沒(méi)有明確指出該類是POJO類,僅供數(shù)據(jù)傳輸使用。實(shí)際上,很多語(yǔ)言都可以聲明只攜帶數(shù)據(jù)的類,如Scala的case類,Kotlin的data類以及C#的record類,它們被證明是很有用的。為此,Java 15將使用記錄record來(lái)建模POJO類,從而方便簡(jiǎn)潔地聲明某個(gè)類只攜帶數(shù)據(jù),而不會(huì)改變類的狀態(tài),同時(shí)自動(dòng)實(shí)現(xiàn)一些用于數(shù)據(jù)生產(chǎn)/消費(fèi)的方法。

另一個(gè)常常與JEP一起出現(xiàn)的是JSR(Java Specification Request,Java規(guī)范提案)。有時(shí)人們想開(kāi)發(fā)一些實(shí)驗(yàn)性特性,例如探索新奇的點(diǎn)子、實(shí)現(xiàn)特性的原型,或者增強(qiáng)當(dāng)前特性,在這些情況下都可以提出JEP,讓社區(qū)也參與實(shí)現(xiàn)或者討論。這些JEP中的少數(shù)可能會(huì)隨著技術(shù)的發(fā)展愈發(fā)成熟,此時(shí),JSR可以將這些成熟的技術(shù)提案進(jìn)一步規(guī)范化,產(chǎn)生新的語(yǔ)言規(guī)范,或者修改當(dāng)前語(yǔ)言規(guī)范,將它們加入Java語(yǔ)言標(biāo)準(zhǔn)中。

Java虛擬機(jī)

簡(jiǎn)單定義下的JDK包括Java虛擬機(jī)和Java語(yǔ)言庫(kù),除了JDK級(jí)別的深度定制,歷史上也存在許多Java虛擬機(jī)實(shí)現(xiàn)。

1)Graal VM:有一統(tǒng)天下野心的通用語(yǔ)言虛擬機(jī)平臺(tái),具體將在1.5節(jié)詳細(xì)討論。

2)Substrate VM:在靜態(tài)編譯時(shí)分析并發(fā)現(xiàn)代碼依賴的所有JDK類和用戶類,然后完全使用靜態(tài)編譯將它們打包成一個(gè)獨(dú)立的二進(jìn)制程序,具體將在1.5節(jié)詳細(xì)討論。

3)JRockit JVM:曾是最快的Java虛擬機(jī),主要面向服務(wù)端應(yīng)用場(chǎng)景,不提供解釋器,所有Java代碼均使用JIT編譯。JRockit的JFR(JavaFlight Record,Java飛行記錄器)功能現(xiàn)已被吸收進(jìn)HotSpot VM。

4)Apache Harmony:Apache基金會(huì)主導(dǎo)的開(kāi)源Java虛擬機(jī)項(xiàng)目,由于Sun公司的態(tài)度導(dǎo)致Harmony項(xiàng)目只有一個(gè)受限的TCK,在Oracle公司收購(gòu)Sun公司后沖突進(jìn)一步延續(xù)。出于這些原因,Apache基金會(huì)宣布退出JCP,同時(shí)Harmony的主導(dǎo)者IBM加入OpenJDK項(xiàng)目,Harmony日漸衰落。

5)Dalvik:為Android系統(tǒng)量身定做的基于寄存器的虛擬機(jī)實(shí)現(xiàn)。

將.class轉(zhuǎn)換為專屬的.dex然后運(yùn)行。.dex是轉(zhuǎn)為Dalvik設(shè)計(jì)的一種壓縮格式,適合內(nèi)存和處理器速度有限的系統(tǒng)。

6)ART:為Android系統(tǒng)量身定做的虛擬機(jī),用于替換之前的Dalvik。Dalvik虛擬機(jī)每次運(yùn)行應(yīng)用程序時(shí)都需要經(jīng)過(guò)JIT編譯器,而使用ART(Android Runtime)后,在安裝應(yīng)用程序時(shí)字節(jié)碼就會(huì)被AOT編譯為機(jī)器代碼,加快了應(yīng)用程序的啟動(dòng)時(shí)間,同時(shí)減少了運(yùn)行時(shí)內(nèi)存占用。

7)Jikes RVM:使用Java語(yǔ)言實(shí)現(xiàn)的Java虛擬機(jī),這種使用X語(yǔ)言實(shí)現(xiàn)的X語(yǔ)言(或者X語(yǔ)言的運(yùn)行時(shí)環(huán)境)虛擬機(jī)也被稱為元語(yǔ)言循環(huán)虛擬機(jī)。

8)Azul Zing VM:擁有領(lǐng)先業(yè)界數(shù)十年的C4垃圾回收器和基于LLVM的JIT編譯器Falcon。

9)IBM J9VM:高度模塊化的虛擬機(jī),它將一些組件如垃圾回收器、JIT編譯器、檢測(cè)工具等單獨(dú)抽離出來(lái)構(gòu)成了IBM OMR項(xiàng)目。

10)Microsoft JVM:微軟為了讓IE運(yùn)行Java Applets開(kāi)發(fā)的僅用于Windows平臺(tái)的Java虛擬機(jī),是當(dāng)時(shí)Windows平臺(tái)上性能最好的虛擬機(jī),但是微軟在1997年被Sun公司以侵犯商標(biāo)等罪名控告并輸?shù)袅斯偎竞?,也終止了Microsoft JVM的開(kāi)發(fā)。

除了以上提到的Java虛擬機(jī)外,還有“冠絕天下”的虛擬機(jī)HotSpotVM,它也是本文的主角。

HotSpot VM

橫看成嶺側(cè)成峰,遠(yuǎn)近高低各不同。不同的人從不同的角度看到的HotSpot VM也不盡相同。

從Java應(yīng)用開(kāi)發(fā)者的角度出發(fā),虛擬機(jī)如圖1-1所示。

Java應(yīng)用開(kāi)發(fā)者關(guān)注Java語(yǔ)言,關(guān)注應(yīng)用的實(shí)現(xiàn)和庫(kù)的實(shí)現(xiàn),用合法的Java代碼表達(dá)思想,通過(guò)編譯器工具編譯產(chǎn)出字節(jié)碼交給虛擬機(jī)運(yùn)行。在他們眼中虛擬機(jī)是一個(gè)黑盒,所以更期望虛擬機(jī)的行為能遵循Java相關(guān)規(guī)范,這樣才能放心地用語(yǔ)言集實(shí)現(xiàn)應(yīng)用程序或庫(kù),進(jìn)而供用戶使用。

虛擬機(jī)開(kāi)發(fā)者關(guān)注虛擬機(jī)內(nèi)部,在他們眼中,虛擬機(jī)不再是黑盒,而是各個(gè)組件根據(jù)規(guī)則交互的一套“Java操作系統(tǒng)”。當(dāng)上層應(yīng)用出現(xiàn)問(wèn)題時(shí),他們可以從虛擬機(jī)層找出問(wèn)題致因,當(dāng)上層語(yǔ)言需要新特性、新功能,或者下層操作系統(tǒng)提供新特性時(shí),他們可以在虛擬機(jī)層實(shí)現(xiàn),然后以某種方式暴露給上層。

從虛擬機(jī)開(kāi)發(fā)者的角度出發(fā),虛擬機(jī)如圖1-2所示。

本文將從虛擬機(jī)開(kāi)發(fā)者的角度深入虛擬機(jī)內(nèi)部,了解各個(gè)組件的具體實(shí)現(xiàn)和交互方式,探索虛擬機(jī)層是如何實(shí)現(xiàn)上層特性的。

源碼模塊

本文主要描述位于openjdk/src/hotspot目錄的Java虛擬機(jī)HotSpot VM的實(shí)現(xiàn)。HotSpot VM根據(jù)目錄可以分為很多模塊,每個(gè)模塊的功能大致如下。

├── cpu # 與CPU架構(gòu)相關(guān)的代碼├── os # 與操作系統(tǒng)相關(guān)的代碼

├── os_cpu # 與CPU和操作系統(tǒng)相關(guān)的代碼

└── share

├── adlc # 平臺(tái)描述語(yǔ)言編譯器(編譯cpu目錄中的*.ad文件)

├── aot # AOT支持,加載驗(yàn)證AOT庫(kù)等

├── asm # 宏匯編器,為宏形式的JIT代碼生成機(jī)器代碼

├── c1 # Client即時(shí)編譯器(C1 JIT)

├── ci # 編譯器接口,定義JIT編譯器通用的一些結(jié)構(gòu)

├── classfile # 字節(jié)碼文件解析和處理

├── code # 描述JIT編譯后的代碼結(jié)構(gòu)等

├── compiler # JIT編譯器代理,虛擬機(jī)通過(guò)它選擇特定的JIT編譯器

├── gc # 垃圾回收。gc/shared表示共享代碼,gc/g1,gc/cms表示特定代碼

├── include # 一些JVM函數(shù)和常量的導(dǎo)出

├── interpreter # 模板解釋器和CPP解釋器實(shí)現(xiàn)

├── jfr # 診斷工具Java Flight Record

├── jvmci # JVMCI編譯器接口,可以開(kāi)啟Graal編譯器代替C2

├── libadt # 內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu)

├── logging # 日志記錄模塊

├── memory # 內(nèi)存相關(guān),包括內(nèi)存劃分,metaspace劃分等

├── metaprogramming # 元編程的一些type_traits

├── oops # Java類,對(duì)象在JVM中的表示

├── opto # Server即時(shí)編譯器(C2 JIT)

├── precompiled # 預(yù)編譯文件

├── prims # JNI、JVMTI、Unsafe類具體實(shí)現(xiàn)

├── runtime # 包羅萬(wàn)象的JVM運(yùn)行時(shí)模塊

├── services # HeapDump、MXBean、jcmd、jinfo等輔助工具支持

└── utilities # 工具組件,如hashtable、JSON解析器、elf格式、快排算法等。

構(gòu)建和調(diào)試

本文涉及的源碼是jdk-12+31,操作系統(tǒng)為macOS 10.15.2,CPU型號(hào)為Intel Core i7,JDK構(gòu)建使用slowdebug類型(以下構(gòu)建演示使用fastdebug類型)。如無(wú)特殊說(shuō)明,文中均基于該配置分析和描述源碼。

為了方便讀者自行嘗試,這里給出在三大主流操作系統(tǒng)上構(gòu)建OpenJDK和斷點(diǎn)調(diào)試HotSpot VM的方式。

1. 在Windows上構(gòu)建,用Visual Studio調(diào)試

下載并編譯好freetype,然后安裝cygwin及必要工具,如autoconf、make、zip、unzip,打開(kāi)cygwin,進(jìn)入源碼目錄輸入命令進(jìn)行編譯,如代碼清單1-1所示:

代碼清單1-1 Windows編譯

$ ./configure

--with-freetype-include=/your_path/freetype-2.9.1/src/include

--with-freetype-lib=/your_path/freetype-2.9.1/lib

--with-boot-jdk=/your_path/openjdk-12-x64_bin

--disable-warnings-as-errors

--with-toolchain-version=2017

--with-target-bits=64 --enable-debug

$ make all # 構(gòu)建OpenJDK$ make hotspot-ide-project # 生成vs項(xiàng)目文件

生成的vs工程文件位于build目錄下的

ide/hotspot-visualstudio/jvm.vcproj中,使用Visual Studio雙擊載入即可,在菜單欄選擇server-fastdebug即可開(kāi)始調(diào)試。在調(diào)試時(shí)若遇到如圖1-3所示的異常提示(safefetch32拋出異常),屬于正常情況,繼續(xù)調(diào)試即可。該異常會(huì)被外部SEH捕獲。

2. 在macOS上構(gòu)建,用Xcode調(diào)試

可以在macOS平臺(tái)下載brew,然后使用brew安裝hg、freetype、ccache,如代碼清單1-2所示:

代碼清單1-2 macOS編譯

$ brew install ccache

$ brew install freetype

$ cd openjdk12

$ chmod +x configure

$ ./configure --enable-ccache --witt-debug-level=fastdebug

$ make all # or make hotspot

一切完成后,

openjdk12/build/macos-x86_64-server-fastdebug/jdk就是編譯產(chǎn)出。打開(kāi)Xcode創(chuàng)建一個(gè)項(xiàng)目,選擇macOS創(chuàng)建一個(gè)命令行項(xiàng)目,然后選中新項(xiàng)目自動(dòng)創(chuàng)建的文件右鍵刪除,接著配置啟動(dòng)項(xiàng)。對(duì)著停止方塊按鈕旁邊的按鈕右鍵Edit Scheme,在“運(yùn)行”中選擇basicconfiguration,并選擇other。這之后需要選擇之前編譯出的jvm,比如/build/macosx-x86_64-server-fastdebug/jdk/bin/java。繼續(xù)選擇Argument,為虛擬機(jī)增加一個(gè)啟動(dòng)參數(shù),用javac編譯得到字節(jié)碼文件,用-cp指定字節(jié)碼所在目錄,后面加上類名。然后選中工程add files toproject,將HotSpot源代碼導(dǎo)入項(xiàng)目。

到這里已經(jīng)可以運(yùn)行了,但是會(huì)出現(xiàn)sigsegv信號(hào),這是正常情況,可以在lldb中使用process handle SIGSEGV -s false命令忽略sigsegv。不過(guò)這種方法在每次運(yùn)行時(shí)都需要輸入該指令,比較麻煩。也可以設(shè)置符號(hào)斷點(diǎn)忽略sigsegv信號(hào),具體操作是選擇左邊創(chuàng)建箭頭,然后在最下面單擊加號(hào)選擇symbolic breakpoint,任意加一個(gè)斷點(diǎn),比如忽略Threads::create_vm模塊的sigsegv。最終效果1-4所示。

3. 在Linux上構(gòu)建,用Visual Code調(diào)試

Linux和macOS的編譯方式基本類似,安裝了必要工具和組件后,配置并運(yùn)行即可,如代碼清單1-3所示:

代碼清單1-3 CentOS編譯

$ yum install java-11-openjdk* # 安裝Bootstrap JDK

$ yum install autoconfunzip zip alsa-lib-devel

$ yum install libXtst-devel libXt-devel libXrender-devel

$ yum install cups-devel freetype-devel fontconfig-devel

$ cd openjdk12

$ chmod +x configure$./configure --with-debug-level=fastdebug

$ make all

在Linux開(kāi)發(fā)機(jī)上可以使用Visual Code進(jìn)行調(diào)試。Visual Code也是筆者推薦使用的智能編輯器,它同時(shí)支持Linux/Windows/macOS三大平臺(tái),只需簡(jiǎn)單的launch.json配置即可進(jìn)行斷點(diǎn)調(diào)試。

具體操作是在Visual Code菜單中選擇File→Open,打開(kāi)OpenJDK 12源碼目錄,然后選擇Debug→Start Debugging添加launch.json文件,如代碼清單1-4所示:

代碼清單1-4 Visual Codelaunch.json

{"version":"0.2.0","configurations": [{"cwd":"${workspaceFolder}","name":"HotSpot Linux Debug","type":"cppdbg","request":"launch","program":"<構(gòu)建生成的JDK目錄>","args": ["<JVM啟動(dòng)參數(shù)>"],"setupCommands": [{"description":"ignore sigsegv","ignoreFailures":false,"text":"handle SIGSEGV nostop"}]}]}

如圖1-5所示,打上斷點(diǎn)后,點(diǎn)擊調(diào)試按鈕即可開(kāi)始調(diào)試。-XX:+PauseAtStartup和-XX:+PauseAtExit參數(shù)分別代表讓虛擬機(jī)在啟動(dòng)和退出的地方停頓。

隨著社區(qū)的不斷發(fā)展,JDK的構(gòu)建愈發(fā)成熟和簡(jiǎn)單,讀者如果在構(gòu)建過(guò)程中遇到問(wèn)題,可以嘗試根據(jù)報(bào)錯(cuò)自行解決,可以參見(jiàn)官方提供的構(gòu)建文檔(openjdk/doc/building.html),也可以在互聯(lián)網(wǎng)中尋求解決方案。構(gòu)建一個(gè)可調(diào)試的虛擬機(jī)是探索虛擬機(jī)實(shí)現(xiàn)的第一步,也是必要的一步。

回歸測(cè)試

當(dāng)為虛擬機(jī)添加或者修改某些功能時(shí),新增對(duì)應(yīng)的測(cè)試是有必要的。常用的測(cè)試虛擬機(jī)和JDK的工具是jtreg。jtreg是JDK測(cè)試框架的一部分,它主要用于回歸測(cè)試[1],當(dāng)然也可以用于單元測(cè)試、功能測(cè)試等。

下面簡(jiǎn)單展示jtreg的使用方法。假設(shè)我們想為HotSpot VM新增一個(gè)虛擬機(jī)參數(shù)-XX:+DummyPrint,在開(kāi)啟時(shí)輸出“Hello World”。為了實(shí)現(xiàn)該功能,可以在

hotspot/share/runtime/globals.hpp文件中新增如代碼清單1-5所示的代碼:

代碼清單1-5?添加DummyPrint參數(shù)

develop(bool, DummyPrint,false, \"Print hello world on the screen") \

然后在

hotspot/share/runtime/thread.cpp的Threads::create_vm()函數(shù)的尾部增加一段代碼,如代碼清單1-6所示:

代碼清單1-6 DummyPrint功能實(shí)現(xiàn)

jint Threads::create_vm(JavaVMInitArgs* args,bool* canTryAgain) {...if(DummyPrint){tty->print_cr("Hello World");}returnJNI_OK;}

修改完后,使用make hotspot增量式構(gòu)建項(xiàng)目,然后附加虛擬機(jī)參數(shù)-XX:+DummyPrint進(jìn)行測(cè)試,結(jié)果應(yīng)該符合功能預(yù)期。但是要想確保新增的代碼在較長(zhǎng)的軟件生命周期內(nèi)正常運(yùn)行,手動(dòng)測(cè)試仍然顯得太過(guò)麻煩。為了解決這個(gè)問(wèn)題,可以使用自動(dòng)回歸測(cè)試。在

openjdk/test/hotspot/jtreg/下新增測(cè)試文件TestDummy.java,如代碼清單1-7所示:

代碼清單1-7 TestDummy.java

/*

* @test TestDummy

* @summary Test whether flag -XX:+DummyPrint works correctly

* @library /test/lib

* @run main/othervm TestDummy

* @author kelthuzadx

*/importjdk.test.lib.process.OutputAnalyzer;importjdk.test.lib.process.ProcessTools;publicclassTestDummy{staticclassWrap{publicstaticvoidmain(String... args){} }staticvoidrunWithFlag(booleanenableFlag)throwsThrowable{ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(enableFlag ?"-XX:+DummyPrint":"-XX:-DummyPrint",Wrap.class.getName());OutputAnalyzer out =newOutputAnalyzer(pb.start());if(enableFlag){out.shouldContain("Hello World");}else{out.shouldNotContain("Hello World");}}publicstaticvoidmain(String[] args)throwsThrowable{runWithFlag(true); runWithFlag(false);}}

自行構(gòu)建jtreg或者下載預(yù)構(gòu)建的jtreg,使用如代碼清單1-8所示的命令進(jìn)行測(cè)試:

代碼清單1-8 jtreg命令

$./jtreg -jdk:<待測(cè)試的JDK路徑> openjdk/test/hotspot/jtreg/TestDummy.javaTest results: passed: 1

如果測(cè)試成功,則會(huì)看到passed字樣,失敗則會(huì)出現(xiàn)failed字樣??梢栽趈treg工作目錄下的JTWork/TestDummy.jtr日志文件中找到詳細(xì)失敗原因。

jtreg的核心是文件頭注釋中的各種符號(hào),其中:@summary用于總結(jié)該測(cè)試的用途和測(cè)試內(nèi)容;@library用于指定一個(gè)或多個(gè)路徑名或者jar文件,如果是多個(gè)可使用空格隔開(kāi);@run用于指定以何種方式運(yùn)行此測(cè)試。更多關(guān)于jtreg符號(hào)的詳細(xì)用法可參見(jiàn)其相關(guān)文檔。

Graal VM

如果說(shuō)HotSpot VM代表了傳統(tǒng)的Java保守陣營(yíng),那么Graal VM無(wú)疑是Java改革陣營(yíng)的代表。

大部分腳本語(yǔ)言或者有動(dòng)態(tài)特性的語(yǔ)言(比如CPython、Lua、Erlang、Java、Ruby、R、JS、PHP、Perl、APL等)都需要用到一個(gè)語(yǔ)言虛擬機(jī),但是這些語(yǔ)言的虛擬機(jī)實(shí)現(xiàn)差別很大,比如CPython/PHP的虛擬機(jī)性能相對(duì)較差,Java的HotSpot VM、C#的CLR和JS的v8卻是業(yè)界頂尖級(jí)別。那么,能不能付出較小努力,用一個(gè)業(yè)界頂尖級(jí)別的虛擬機(jī)來(lái)運(yùn)行這些語(yǔ)言,享受該虛擬機(jī)的一些工匠特性,如GC、鎖優(yōu)化、JIT編譯器呢?

答案是肯定的。首先,對(duì)于Java、Scala、Groovy這些本來(lái)就是基于JVM的語(yǔ)言,通過(guò)編譯器前端工具得到Java字節(jié)碼后直接在JVM上運(yùn)行即可。對(duì)于CPython、R、Ruby、PHP、Perl乃至自己寫的一門新的語(yǔ)言,其開(kāi)發(fā)流程一般分為如下4個(gè)階段:

1)首先解析源代碼到AST(Abstract Syntax Tree,抽象語(yǔ)法樹(shù)),寫一個(gè)AST解釋器。

2)當(dāng)有人使用這門語(yǔ)言時(shí),語(yǔ)言設(shè)計(jì)者可以繼續(xù)迭代,實(shí)現(xiàn)一個(gè)完整的語(yǔ)言虛擬機(jī),包括GC、運(yùn)行時(shí)等,代碼的執(zhí)行仍然使用AST解釋器。

3)用的人多了,語(yǔ)言繼續(xù)迭代,將AST轉(zhuǎn)換為字節(jié)碼,代碼執(zhí)行使用字節(jié)碼解釋器。4)用的人特別多,性能也很關(guān)鍵,如果這個(gè)語(yǔ)言社區(qū)有足夠的資金和人力,那么可以寫JIT編譯器,提升GC性能等,不過(guò)大部分語(yǔ)言都到不了這一步。

一門語(yǔ)言至少要達(dá)到階段3才算基本滿足工業(yè)生產(chǎn)的要求,但是人們希望一門語(yǔ)言在階段1時(shí)性能就足夠好,而不用花那么多精力和財(cái)力達(dá)到階段3甚至階段4,這就是Truffle語(yǔ)言實(shí)現(xiàn)框架出現(xiàn)的原因。

Truffle是一個(gè)Java框架,自然運(yùn)行在JVM上。在這個(gè)框架下,用戶只需要實(shí)現(xiàn)具體語(yǔ)言的AST解釋器,付出的努力比較小,性能也足夠好。因?yàn)門ruffle框架可以使AST在解釋過(guò)程中根據(jù)節(jié)點(diǎn)的類型反饋信息對(duì)節(jié)點(diǎn)進(jìn)行變形,也可以在AST解釋過(guò)程中進(jìn)行部分求值(PartialEvaluation)[1],將這個(gè)AST的一部分節(jié)點(diǎn)編譯為機(jī)器代碼,不用解釋執(zhí)行AST節(jié)點(diǎn),即可直接執(zhí)行。

Truffle將AST節(jié)點(diǎn)編譯為機(jī)器代碼使用的編譯器是Graal,這是一個(gè)用Java編寫的即時(shí)編譯器。前面提到,Truffle是一個(gè)Java框架,那么一個(gè)用Java語(yǔ)言編寫的即時(shí)編譯器要如何編譯Java代碼呢?答案是通過(guò)JEP243的JVMCI。JVM是用C++語(yǔ)言編寫的,在JVM中內(nèi)置了兩個(gè)用C++編寫的即時(shí)編譯器,C1和C2。一般頻繁的代碼先用C1編譯,這些代碼即熱點(diǎn),如果熱點(diǎn)繼續(xù),則使用C2編譯。JVMCI相當(dāng)于把本該交給C2編譯的代碼交給Graal編譯,然后使用編譯后的代碼。用Java寫即時(shí)編譯器看起來(lái)很神奇,其實(shí)很正常,因?yàn)榧磿r(shí)編譯說(shuō)到底就是將一段byte[]代碼在運(yùn)行時(shí)轉(zhuǎn)換為另一段byte[]代碼,可以用任何語(yǔ)言實(shí)現(xiàn),只是實(shí)現(xiàn)過(guò)程中的難易程度不同。

到目前為止,Java、Scala、Groovy已經(jīng)可以在JVM上運(yùn)行了,CPython、R、Ruby、JS通過(guò)Truffle框架實(shí)現(xiàn)一個(gè)AST解釋器后也可以在JVM上運(yùn)行。那么如何處理如C/C++、Go、Fortran這類靜態(tài)語(yǔ)言呢?對(duì)于這個(gè)問(wèn)題,Graal VM給出的解決方案是Sulong框架。用戶用一些工具(如clang)將C/C++這類語(yǔ)言轉(zhuǎn)換為L(zhǎng)LVM IR,然后使用基于Truffle的AST解釋器解釋LLVM IR。這里基于Truffle的AST解釋器就是Sulong,如圖1-6所示。

現(xiàn)在絕大部分語(yǔ)言都可以在JVM上運(yùn)行了,將上面提到的所有技術(shù)放到一起,這個(gè)整體就叫作Graal VM。Graal VM就像皇帝的新衣,人人都在討論,但是如果要回答它到底是什么卻言之無(wú)物。實(shí)際上Graal VM這個(gè)語(yǔ)言虛擬機(jī)并不是真正存在的,Graal VM是指以Java虛擬機(jī)為基礎(chǔ),以Graal編譯器為核心,以能運(yùn)行多種語(yǔ)言為目標(biāo),包含一系列框架和技術(shù)的大雜燴,如圖1-7所示。

但這并不是Graal VM的全部。圖1-7中的所有語(yǔ)言最終都運(yùn)行在JVM上,需要運(yùn)行機(jī)器提前安裝JDK環(huán)境。JVM由于自身原因,啟動(dòng)速度比較慢,內(nèi)存負(fù)載較高。那么,能不能把程序直接打包成平臺(tái)相關(guān)的可執(zhí)行文件,后面直接執(zhí)行這個(gè)可執(zhí)行文件,而不依賴JVM呢?

交出這份答卷的是Substrate VM。Substrate VM借助Graal編譯器,可以將Java程序AOT編譯為可執(zhí)行程序。它首先通過(guò)靜態(tài)分析找到Java程序用到的所有類、方法和字段以及一個(gè)非常小的SVM運(yùn)行時(shí),然后對(duì)這些代碼進(jìn)行AOT編譯,生成一個(gè)可執(zhí)行文件。

Substrate VM的想法很美好,但是在實(shí)踐中會(huì)遇到諸多問(wèn)題,因?yàn)镴ava有反射等動(dòng)態(tài)特性,這些特性可能導(dǎo)致新類加載無(wú)法通過(guò)靜態(tài)分析解決。目前Substrate VM的GC是一個(gè)比較簡(jiǎn)單的分代GC,缺少很多調(diào)試工具和性能分析支持,編譯速度較慢,不過(guò)這些都在慢慢完善,生產(chǎn)環(huán)境上也有阿里巴巴和Twitter等公司在不斷嘗試Substrate VM的實(shí)際落地,并取得了顯著的效果。

本章小結(jié)

1.1節(jié)介紹了各具特色的JDK分支和OpenJDK的子項(xiàng)目。1.2節(jié)介紹了Java改進(jìn)提案,它們代表類Java社區(qū)最新的工作動(dòng)向。1.3節(jié)簡(jiǎn)單描述了歷史長(zhǎng)河中存在或者曾經(jīng)存在的Java虛擬機(jī)。1.4節(jié)討論了HotSpotVM的組件、源碼結(jié)構(gòu)、構(gòu)建、調(diào)試以及修改代碼后如何回歸測(cè)試。最后1.5節(jié)展望未來(lái),討論了Java的前沿技術(shù)Graal VM。

本文給大家講解的內(nèi)容是Java生態(tài)系統(tǒng),介紹JDK、JVM、JEP,帶領(lǐng)大家走進(jìn)虛擬機(jī)

原文鏈接:

https://www.toutiao.com/a6932714185818259982/?log_from=dc90ac6bbcf4f_1637993481184

?著作權(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)容