Hollis原創|深入分析Java的編譯原理

HollisChuang發表於2019-05-14

GitHub 2.6k Star 的Java工程師成神之路 ,不來了解一下嗎?

GitHub 2.6k Star 的Java工程師成神之路 ,真的不來了解一下嗎?

GitHub 2.6k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎?

在《Java程式碼的編譯與反編譯》中,有過關於Java語言的編譯和反編譯的介紹。我們可以通過javac命令將Java程式的原始碼編譯成Java位元組碼,即我們常說的class檔案。這是我們通常意義上理解的編譯。

但是,位元組碼並不是機器語言,要想讓機器能夠執行,還需要把位元組碼翻譯成機器指令。這個過程是Java虛擬機器做的,這個過程也叫編譯。是更深層次的編譯。

在編譯原理中,把原始碼翻譯成機器指令,一般要經過以下幾個重要步驟:

QQ20180414-203816

根據完成任務不同,可以將編譯器的組成部分劃分為前端(Front End)與後端(Back End)。

前端編譯主要指與源語言有關但與目標機無關的部分,包括詞法分析、語法分析、語義分析與中間程式碼生成。

後端編譯主要指與目標機有關的部分,包括程式碼優化和目的碼生成等。

我們可以把將.java檔案編譯成.class的編譯過程稱之為前端編譯。把將.class檔案翻譯成機器指令的編譯過程稱之為後端編譯。

Java中的前端編譯

前端編譯主要指與源語言有關但與目標機無關的部分,包括詞法分析、語法分析、語義分析與中間程式碼生成。

我們所熟知的javac的編譯就是前端編譯。除了這種以外,我們使用的很多IDE,如eclipse,idea等,都內建了前端編譯器。主要功能就是把.java程式碼轉換成.class程式碼。

詞法分析

詞法分析階段是編譯過程的第一個階段。這個階段的任務是從左到右一個字元一個字元地讀入源程式,將字元序列轉換為標記(token)序列的過程。這裡的標記是一個字串,是構成原始碼的最小單位。在這個過程中,詞法分析器還會對標記進行分類。

詞法分析器通常不會關心標記之間的關係(屬於語法分析的範疇),舉例來說:詞法分析器能夠將括號識別為標記,但並不保證括號是否匹配。

語法分析

語法分析的任務是在詞法分析的基礎上將單詞序列組合成各類語法短語,如“程式”,“語句”,“表示式”等等.語法分析程式判斷源程式在結構上是否正確.源程式的結構由上下文無關文法描述。

語義分析

語義分析是編譯過程的一個邏輯階段, 語義分析的任務是對結構上正確的源程式進行上下文有關性質的審查,進行型別審查。語義分析是審查源程式有無語義錯誤,為程式碼生成階段收集型別資訊。

語義分析的一個重要部分就是型別檢查。比如很多語言要求陣列下標必須為整數,如果使用浮點數作為下標,編譯器就必須報錯。再比如,很多語言允許某些型別轉換,稱為自動型別轉換。

中間程式碼生成

在源程式的語法分析和語義分析完成之後,很多編譯器生成一個明確的低階的或類機器語言的中間表示。該中間表示有兩個重要的性質: 1.易於生成; 2.能夠輕鬆地翻譯為目標機器上的語言。

在Java中,javac執行的結果就是得到一個位元組碼,而這個位元組碼其實就是一種中間程式碼。

PS:著名的解語法糖操作,也是在javac中完成的。

Java中的後端編譯

首先,我們大家都知道,通常通過 javac 將程式原始碼編譯,轉換成 java 位元組碼,JVM 通過解釋位元組碼將其翻譯成對應的機器指令,逐條讀入,逐條解釋翻譯。很顯然,經過解釋執行,其執行速度必然會比可執行的二進位制位元組碼程式慢很多。這就是傳統的JVM的**直譯器(Interpreter)**的功能。為了解決這種效率問題,引入了 JIT 技術。

JAVA程式還是通過直譯器進行解釋執行,當JVM發現某個方法或程式碼塊執行特別頻繁的時候,就會認為這是“熱點程式碼”(Hot Spot Code)。然後JIT會把部分“熱點程式碼”翻譯成本地機器相關的機器碼,並進行優化,然後再把翻譯後的機器碼快取起來,以備下次使用。

HotSpot虛擬機器中內建了兩個JIT編譯器:Client Complier和Server Complier,分別用在客戶端和服務端,目前主流的HotSpot虛擬機器中預設是採用直譯器與其中一個編譯器直接配合的方式工作。

當 JVM 執行程式碼時,它並不立即開始編譯程式碼。首先,如果這段程式碼本身在將來只會被執行一次,那麼從本質上看,編譯就是在浪費精力。因為將程式碼翻譯成 java 位元組碼相對於編譯這段程式碼並執行程式碼來說,要快很多。第二個原因是最優化,當 JVM 執行某一方法或遍歷迴圈的次數越多,就會更加了解程式碼結構,那麼 JVM 在編譯程式碼的時候就做出相應的優化。

在機器上,執行java -version命令就可以看到自己安裝的JDK中JIT是哪種模式:

javaversion

上圖是我的機器上安裝的jdk1.8,可以看到,他是Server Compile,但是,需要說明的是,無論是Client Complier還是Server Complier,直譯器與編譯器的搭配使用方式都是混合模式,即上圖中的mixed mode。

熱點檢測

上面我們說過,要想觸發JIT,首先需要識別出熱點程式碼。目前主要的熱點程式碼識別方式是熱點探測(Hot Spot Detection),有以下兩種:

1、基於取樣的方式探測(Sample Based Hot Spot Detection) :週期性檢測各個執行緒的棧頂,發現某個方法經常出險在棧頂,就認為是熱點方法。好處就是簡單,缺點就是無法精確確認一個方法的熱度。容易受執行緒阻塞或別的原因干擾熱點探測。

2、基於計數器的熱點探測(Counter Based Hot Spot Detection)。採用這種方法的虛擬機器會為每個方法,甚至是程式碼塊建立計數器,統計方法的執行次數,某個方法超過閥值就認為是熱點方法,觸發JIT編譯。

在HotSpot虛擬機器中使用的是第二種——基於計數器的熱點探測方法,因此它為每個方法準備了兩個計數器:方法呼叫計數器和回邊計數器。

方法計數器。顧名思義,就是記錄一個方法被呼叫次數的計數器。

回邊計數器。是記錄方法中的for或者while的執行次數的計數器。

編譯優化

前面提到過,JIT除了具有快取的功能外,還會對程式碼做各種優化。說到這裡,不得不佩服HotSpot的開發者,他們在JIT中對於程式碼優化真的算是面面俱到了。

這裡簡答提及幾個我覺得比較重要的優化技術,並不準備直接展開,讀者感興趣的話,我後面再寫文章單獨介紹。

逃逸分析、 鎖消除、 鎖膨脹、 方法內聯、 空值檢查消除、 型別檢測消除、 公共子表示式消除

Hollis原創|深入分析Java的編譯原理

相關文章