一問帶你深入分析Java的編譯原理

在之前的文章中,有過關於Java語言的編譯和反編譯的介紹。我們可以通過javac命令將Java程序的源代碼編譯成Java字節碼,即我們常說的class文件。這是我們通常意義上理解的編譯。

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

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

一問帶你深入分析Java的編譯原理

根據完成任務不同,可以將編譯器的組成部分劃分為前端(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是哪種模式:

一問帶你深入分析Java的編譯原理

上圖是我的機器上安裝的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中對於代碼優化真的算是面面俱到了。

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

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

相關推薦

推薦中...