摘要:OpenJDK的開源吸引了很多想弄明白Java虛擬機如何運行的開發(fā)人員。本文基于HotSpot虛擬機源碼,分析了Java虛擬機的運行機制,并進一步深入研究了服務(wù)器端C2即時編譯框架,指出了HotSpot虛擬機高效運行的原因,為下一步深入優(yōu)化打好基礎(chǔ)。
關(guān)鍵詞: Java虛擬機;HotSpot虛擬機;服務(wù)器端編譯器;結(jié)構(gòu)描述文件
一、引言
Java虛擬機技術(shù)提供Java標準平臺的基礎(chǔ)設(shè)施,提供對快速開發(fā)、部署關(guān)鍵業(yè)務(wù)的桌面和企業(yè)應(yīng)用程序的解決方案。Java無處不在,而Java虛擬機正是支撐Java運行的秘密武器,它是一個在硬件平臺、操作系統(tǒng)之上的一個龐大復(fù)雜的軟件,涉及的理論和技術(shù)非常廣而寬。
HotSpot虛擬機是Sun/Oracle JDK和OpenJDK的默認Java虛擬機[1],是基于Java虛擬機規(guī)范[2]的一個高效虛擬機實現(xiàn),也是全世界使用最廣泛、最具影響力的Java虛擬機。很多程序員默認HotSpot虛擬機等同于Java虛擬機。
二、HotSpot虛擬機
(二)執(zhí)行架構(gòu)
HotSpot虛擬機的執(zhí)行架構(gòu)示意圖如圖1所示[4-5]。
HotSpot虛擬機采用解釋器與編譯器并存的架構(gòu),解釋器和編譯器是相輔相成地配合工作的。解釋器和編譯器兩者各有優(yōu)勢:當程序需要迅速啟動和執(zhí)行的時候,解釋器可以省去編譯的時間,立即執(zhí)行;當程序運行后,隨著時間的推移,編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼之后,可以獲取更高的執(zhí)行效率。在某些特定情況下編譯模式也能夠通過“逆優(yōu)化”(deoptimization)退回到解釋模式下繼續(xù)執(zhí)行。
(二)解釋編譯交互
同時存在解釋執(zhí)行和編譯執(zhí)行,涉及解釋執(zhí)行和編譯執(zhí)行互相轉(zhuǎn)化,圖2說明了解釋執(zhí)行和編譯執(zhí)行互相轉(zhuǎn)換的途徑[6]。
Java虛擬機中如果某個方法被編譯,則下次執(zhí)行同樣的方法時切換為編譯執(zhí)行。編譯執(zhí)行的入口有2種情況:方法編譯和核心循環(huán)編譯。方法編譯在下次執(zhí)行前要切換方法調(diào)用的入口,改寫成i2c adapter的首地址,這個adapter完成從解釋執(zhí)行轉(zhuǎn)為編譯執(zhí)行的功能;同時要完成把解釋執(zhí)行的參數(shù)拷貝到編譯執(zhí)行的參數(shù)區(qū),解釋執(zhí)行時,參數(shù)區(qū)直接位于棧中,對于編譯執(zhí)行,參數(shù)一部分位于寄存器中,一部分位于棧中,所以,移植過程中需要考慮傳遞參數(shù)的這種情況。核心循環(huán)編譯是由循環(huán)體出發(fā)的,但編譯器依然會以整個方法作為編譯對象,執(zhí)行的是棧上替換OSR算法,即在運行過程中直接用編譯執(zhí)行的方法替換解釋執(zhí)行的方法,而不是下次調(diào)用該方法時再做替換,OSR關(guān)鍵的是要復(fù)制解釋執(zhí)行時產(chǎn)生的局部變量,同步鎖等。
編譯執(zhí)行到解釋執(zhí)行的也有2個入口,最普通的入口是編譯執(zhí)行的方法調(diào)用解釋執(zhí)行的方法;另一個入口叫逆(deoptimization)操作。在編譯執(zhí)行的方法中進行方法調(diào)用時會查詢方法的入口地址,如果是靜態(tài)方法,直接解析方法入口地址,否則就將解析方法入口地址,這個函數(shù)會判斷被調(diào)用的方法是否已經(jīng)被編譯過了,如果沒有被編譯過就進入編譯轉(zhuǎn)解釋執(zhí)行的入口。逆優(yōu)化則可能是在編譯執(zhí)行方法時可能由于某種原因需要重新解釋執(zhí)行。
三、C2即時編譯
服務(wù)器版編譯器是一個專門面向服務(wù)器典型應(yīng)用的充分優(yōu)化過的先進自適應(yīng)編譯器,它支持和傳統(tǒng)編譯器如C++編譯器類似的許多編譯優(yōu)化流程,以及一些傳統(tǒng)編譯器所不能做的自適應(yīng)優(yōu)化。
(一)編譯框架
C2即時編譯器通過目標處理器平臺的結(jié)構(gòu)描述文件和指令匹配規(guī)則,提升優(yōu)化效率,其結(jié)構(gòu)圖如圖3所示:
編譯器首先分析字節(jié)碼并生成中間表示Ideal圖,所有優(yōu)化和代碼產(chǎn)生都是基于它;接著進行平臺無關(guān)優(yōu)化并生成平臺相關(guān)的MachNode圖;最后進行平臺相關(guān)優(yōu)化,包括指令選擇、代碼重排、寄存器分配、窺孔優(yōu)化,直至輸出目標機器代碼。
在指令匹配選擇階段,基于確定有限狀態(tài)機生成器(DFA)匹配最優(yōu)的指令和操作數(shù),通過指令的屬性,如指令的訪存代價、流水線結(jié)構(gòu)等眾多屬性分析每種指令的優(yōu)劣并作出最優(yōu)匹配選擇,這些重要的屬性都是通過國產(chǎn)處理器的結(jié)構(gòu)描述文件(Architecture Description File,AD文件)通過結(jié)構(gòu)描述語言編譯器(ADL)編譯生成獲得;接著進行機器平臺相關(guān)的優(yōu)化,如寄存器分配、窺孔優(yōu)化等,直至最后生成熱方法的本地機器代碼,服務(wù)器版編譯器寄存器分配是一個全局圖著色分配器,它可以充分利用處理器的大寄存器集合。
(二)結(jié)構(gòu)描述文件
結(jié)構(gòu)描述文件(Architecture Description File,AD文件)的準確描述,對服務(wù)器版即時編譯器的移植工作非常關(guān)鍵,這是性能版虛擬機高性能的基礎(chǔ)。它描述目標處理器的結(jié)構(gòu),并通過專門的ADL編譯器將結(jié)構(gòu)描述文件創(chuàng)建為JIT包含的結(jié)構(gòu)相關(guān)優(yōu)化源碼,以便JIT生成高效正確的本地代碼。
AD文件描述了三類基本的不同結(jié)構(gòu)特征:目標平臺的指令集(包括操作數(shù))、寄存器(以及寄存器分配相關(guān)信息)以及針對調(diào)度優(yōu)化的目標平臺流水線結(jié)構(gòu)信息,另外還有部分為了簡化描述而增加的一些輔助定義。
1. 寄存器描述
寄存器格式的定義如下:
“reg_def” name ( register save type, C convention save type,
ideal register type, encoding, vm name );
函數(shù)的各個參數(shù)意思明確,“save type”表示寄存器分配寄存器在方法調(diào)用之間的保留類型,有不保存、調(diào)用處保存、調(diào)用前保存及調(diào)用前和調(diào)用處都保存等四種類型;第三個參數(shù)“ideal register type”用來確定如何保留恢復(fù)一個寄存器,“encoding”是由于有寄存器擴展,用于表示放置在opcodes中實際的位數(shù)。
寄存器描述中還包括reg_class和alloc_class,如整形寄存器、浮點寄存器、特殊寄存器(如標志寄存器)以及定義具有相同屬性的寄存器類等,它們整個是為指令選擇和寄存器分配提供信息,所有寄存器均是用戶可見或普通指令涉及的寄存器,不包括特權(quán)或處理器內(nèi)部寄存器。
2. 指令集描述
指令集在結(jié)構(gòu)描述文件中的工作占著很大比重,一是由于申威平臺指令較多,每條指令都需要描述;另一個是還需要描述操作碼、操作數(shù)屬性、考慮如何與中間表示匹配、匯編輸出格式以及硬件執(zhí)行上屬于什么流水分類等。
下面我們以下兩個具體實例來說明這些屬性設(shè)置的含義:
實例1:
1. Instruct addI_reg_reg(iRegI dst, iRegI src1, iRegI src2) %{
2. match(Set dst (AddI src1 src2));
3.
4.? size(4);
5. Format %{ “ADD? ? $src1,$src2,$dst” %}
6. ins_encode %{
7.? ? __ add($src1$$Register, $src2$$Register, $dst$$Register);
8.? %}
9.? ins_pipe(ialu_reg_reg);
10. %}
實例2:
1. Instruct addI_reg_imm13(iRegI dst, iRegI src1, immI13 src2) %{
2. match(Set dst (AddI src1 src2));
3.
4.? size(4);
5.? Format %{ “ADD? ? $src1,$src2,$dst” %}
6.? opcode(Assembler::add_op3, Assembler::arith_op);
7.? ins_encode( form3_rs1_simm13_rd(src1, src2, dst) );
8.? ins_pipe(ialu_reg_imm);
9.? %}
instruct:指定機器指令的入口。實例中說明長字加指令的入口,前一個是指操作數(shù)都為寄存器時addl指令的入口,后面的指操作數(shù)一是寄存器一是13位立即數(shù)時addl指令的入口。
match:指定指令的中間表示匹配規(guī)則。
size:指令默認長度,申威處理器指令長度均為32位。
encode:指定指令的編碼規(guī)則。在instruct定義中,有兩種方式生成本地機器碼,一種是通過ins_encode語句塊直接指定指令序列;另一種是通過opcode和ins_encode配合完成,opcode指定指令的操作碼(包括主操作碼和輔助操作碼),ins_encode指定指令的其他部分如何編碼,這是通過enc_class來完成。
enc_class定義了若干指令編碼類供instruct使用,通過這種方式,可把編碼規(guī)則相同的指令用一個enc_class代替,這樣可有效降低instruct定義的工作量且可降低出錯概率。
ins_pipe:指定當前指令使用哪些pipe_class。這在處理器流水線的特征里進行了描述,包括指令長度、是否有延遲槽等的屬性描述、指令執(zhí)行部件資源描述和不同類型的操作涉及了流水線的哪些階段的流水分類描述等。
format:匯編指令的輸出格式。若為多條指令,應(yīng)一起輸出。
指令描述的其他信息描述也都比較簡單,從文檔中能夠明顯看出,這里不作詳述。指令集定義完成被ADL編譯器生成后,在指令選擇階段利用一個DFA來選擇匹配最優(yōu)的操作數(shù)和指令。
3. 流水線結(jié)構(gòu)
結(jié)構(gòu)描述文件還定義了處理器流水線的特征,它描述了指令執(zhí)行在硬件流水線上的特征。該部分描述涉及四部分:
屬性(attributes):定義了指令長度、是否有延遲槽等。
資源(resources):定義指令執(zhí)行的功能部件。
流水線描述(pipe_desc):定義流水線站臺。
流水線分類(pipe_class):定義不同類型的操作涉及了流水線的哪些階段,,在指令集描述中的ins_pipe就是用于指定指令屬于哪個流水線分類。
(三)指令匹配
在解析Java字節(jié)碼并生成IR,進而形成抽象語法樹(Abstract Syntax Tree, AST)中間文件,樹中的節(jié)點表示諸如加減乘除等操作,其子節(jié)點表示其輸入操作數(shù),比如圖4(a)表示寄存器形式的加法,該加法有兩個整數(shù)寄存器(iRegI)操作數(shù)和一個標志寄存器操作數(shù)。這個樹最終可簡單對應(yīng)到一條本地的addl指令。
如果節(jié)點包含子樹,形成嵌套,則可表示更復(fù)雜的操作,如圖4(b)所示,mull有3個操作數(shù),其運算結(jié)果又作為addl的操作數(shù)之一。該樹可能生成mull和addl兩條本地指令,也可能生成1條乘加指令。對于更復(fù)雜的樹,生成本地指令時可能面臨更復(fù)雜的多樣化選擇。為了生成最優(yōu)的指令,Hotspot中采用自底向上重寫系統(tǒng)(bottom-up rewrite system,BURS)算法[7],根據(jù)目標機器體系結(jié)構(gòu)描述,如本地指令與中間指令的關(guān)系、指令開銷等自下而上匹配AST,選擇最優(yōu)本地指令。
四、結(jié)束語
Java虛擬機與體系結(jié)構(gòu)密切相關(guān),涉及處理器、操作系統(tǒng)核心、編譯器、基礎(chǔ)運行時庫等模塊,涉及機器碼、匯編、C、C++、Java語言,其自身結(jié)構(gòu)復(fù)雜、技術(shù)先進、代碼龐大。為了滿足企業(yè)級應(yīng)用對性能的最高要求,即時編譯模式不斷采用大量靜態(tài)編譯和激進編譯優(yōu)化技術(shù),同時又盡可能降低運行時開銷。
C2即時編譯器是一個專門面向服務(wù)器典型應(yīng)用的充分優(yōu)化過的先進自適應(yīng)編譯器,強調(diào)的是程序運行的峰值性能。它支持許多和C++編譯器支持的相同類型的優(yōu)化,以及一些傳統(tǒng)編譯器所不能做的自適應(yīng)優(yōu)化,如虛擬方法調(diào)用間的積極樂觀內(nèi)聯(lián)(aggressive optimistic inlining)。與靜態(tài)編譯器相比,自適應(yīng)編譯器是非常靈活的,在優(yōu)化層面上有著無可比擬的競爭優(yōu)勢,典型情況下甚至比先進的靜態(tài)分析和編譯技術(shù)更優(yōu)秀、輸出的代碼質(zhì)量更高。
本文深入分析HotSpot虛擬機的運行機制和C2即時編譯框架,為下一步深入面向平臺進行軟硬件協(xié)同優(yōu)化打下好的基礎(chǔ)。
作者單位:鄭艷? ? 無錫城市職業(yè)技術(shù)學(xué)院
參? 考? 文? 獻
[1]周志明. 深入理解Java虛擬機-JAVA虛擬機高級特性與最佳實踐(第2版). 機械工業(yè)出版社, 2013.6
[2] Tim Lindholm, etc. The Java? Virtual Machine Specification. Oracle Corporation, Inc.
[3] Peter B. Kessler. Java HotSpot? Virtual Machine. http://openjdk.java.net/groups/hotspot/docs/FOSDEM-2007-HotSpot.pdf, 2007
[4] Sun Microsystems. The Java HotSpot Virtual Machine. A Technical White Paper.
[5] Michael Paleczny, Christopher Vick, and Cliff Click. The Java HotSpot Server Compiler. In Proceedings of the Java Virtual Machine Research and Technology Symposium, 1-12,USENIX, 2001
[6] ADL語法規(guī)范. JavaSoft HotSpot Architecture Description Language Syntax Specification. 1997.9
[7]Eduardo Pelegrí-Llopart, Susan L. Graham. Optimal Code Generation for Expression Trees: an Application BURS Theory. In Proceedings of the 15th ACM Symposium on Principles of Programming Languages, ACM Press, 294-308, 1988