Javac原理剖析

01。Javac是什麼?

Javac是一種編譯器,能將一種語言規範轉化為另一種語言規範。Javac的任務就是將Java原始碼語言轉化成JVM能夠識別的一種語言,JVM會將JVM語言轉化成當前這個機器能夠識別的機器語言。

Javac原理剖析

Javac的任務就是將Java原始碼成Java位元組碼,也就是JVM能夠識別二進位制碼。從表面上看就是上面的部分將。java檔案轉成。class檔案,而實際上是將Java的原始碼轉化成一連串二進位制數字,這些二進位制數字是有格式的,只有JVM能夠正確識別他們到底表達了什麼意思。

02。Javac編譯器的基本架構

Javac的作用是將符合規範原始碼轉化成為位元組碼,需要哪些過程呢?我們可以複習一下大學時候的編譯原理知識。

Javac原理剖析

如何編譯程式呢?

1。詞法分析:主要是讀取原始碼,一個位元組為一節地讀取進來,找些那些是語言的關鍵字例如,if、else等。詞法分析就是從原始碼中找出一些規範化的Token流,一個形象的比如就是人類能分清楚一個句子的單詞和標點符號。

2。語法分析:檢查這些關鍵詞組合在一起是不是符合Java規範,如在if後面是不是跟著布林表示式,就像人類能分清楚一個句子的主語謂語賓語,檢查是否符合語法。語法分析的結果就是形成一個Java語言規範的抽象語法樹。

3。語義分析:將一些複雜難懂的語法轉化為簡單易懂的語法。這個過程對應於將複雜難懂的文言文轉化為白話文。語義分析的結果是將複雜難懂的語法轉化為最簡單的語法,對應於Java,可以將註解等轉化為抽象語法樹。

最後是程式碼生成將抽象語法樹生成位元組碼,可以對應於人類的將中文轉化為英文組合成新的句子。

03。Javac原理分析

詞法分析器

我們可以透過一個簡單的Java類來進行詞法分析。

Javac原理剖析

我們可以呼叫com。sun。tools。javac。main。Main類來實現手動編譯指定的類。

Javac原理剖析

Javac的主要詞法分析器的結構類是com。sun。tools。javac。parser。Lexer,這個預設實現類是com。sun。tools。javac。parser。Scanner,這個類會逐步讀取Java源程式的單個字元,然後解析出符合Java語言規範的Token序列。所設計的類如下圖:

Javac原理剖析

這兩個Factory生成兩個介面類的Scanner和JavacParser,JavacParser規定哪些詞是符合Java語言規範規定,而具體讀取和歸類不同的詞法操作由Scanner完成,Token規定了完成Java語言的合法關健詞,Names用來儲存和表示解析後的詞法。

詞法分析額過程是在JavacParser的parseCompilationUnit()方法中完成,主要程式碼如下:

Javac原理剖析

Javac原理剖析

Javac原理剖析

Javac原理剖析

這段程式碼表示從原始檔的一個字元開始,按照Java語法規範依次出現packege、import、類定義,以及屬性和方法定義等,最後構建一個抽象的語法樹。

詞法分析器的分析結構就是將這個列中的所有關鍵詞匹配到Token類中的所有項中的任何一項。上述程式碼分析的Token流是:

Javac原理剖析

這裡有兩個關鍵點是:Javac是如何分辨這一個個Token的呢?還有就是Javac如何知道啊哪些字元組合在一起就是一個Token的呢?

第一個問題的答案是這樣的:Javac在進行詞法分析時會由javacParser根據Java語言規範來控制什麼順序、什麼地方應該出現什麼Token。

判斷當前的Token是不是Token。PACKAGE,使用qualident方法。這個方法的原始碼是:

Javac原理剖析

上述程式碼的執行流程如下:

Javac原理剖析

透過上圖,我們明白了Token的順序規則,之後我們來解決下一個問題,如何判斷哪些字元組合是一個Token 的規則是在Scanner的nextToken方法中定義,沒呼叫一次這個方法會構成一個Token,而這個Token必須是com。sun。tools。javac。parser。Token中的任何元素之一。

那麼如何將讀取每個token轉化呢?這個任務是在com。sun。tools。javac。parser。Keywords類中完成,Keywords負責將所有字元集合對應到Token集合中。

字元集合到Token轉換相關的類關係如下圖

Javac原理剖析

每個字元集合都會是一個Name物件,所有的Name物件都儲存在Name。Table這個內部類中,這個類也就是對應的這類的符號表。而Keywords會將Token的對應關係,這個關係儲存在Keywords類的key陣列中,這個key陣列只儲存了在com。sun。tools。javac。parser。Token類中定義的所有Token到Name物件的關係,而其他的所有字元集合Keywords都會將它對應到Token。IDENTIFIER型別。

字元集合轉化成Name物件,Name物件對應到Token的轉換關係如下圖;

Javac原理剖析

語法分析器

語法分析器是將詞法分析器分析的Token流建成更加結構化的語法樹,也就是將一個個單片語裝成一句話,一個完整的語句。

Javac的語法樹使得Java原始碼更加結構化,這種結構化可以為後面進一步處理提供方便。每個語法書上的節點都是com。sun。tools。javac。tree。JCTree的一個例項。

關於語法規則是:

1。每一個語法節點都會實現一個介面xxxTree,這個介面繼承自com。sun。source。tree。Tree介面。

2。每個語法節點都是com。sun。tools。javac。tree。JCTree的子類,並且會實現第一節點中的xxxTree介面類,這個類的名稱類似於JCxxx。

3。所有的JCxxx類都作為一個靜態內部類定義在JCTree類中。

Javac原理剖析

JCTree類中有如下三個重要的屬性項:

1。Tree tag:每個語法節點都會用一個整形常數表示,並且每個節點型別的數值是在其哪一個的基礎上加1,頂層節點TOPLEVEL是1,而IMPORT節點等於TOPLEVEL加1,等於2。

2。pos:也是一個整數,它儲存的是這個語法節點在原始碼中的起始位置,一個檔案的位置是0,而-1表示不存在。

3。type:它表示的是這個節點是什麼Java型別,如是int、float還是String。

在package的詞法分析的過程是

Javac原理剖析

這行程式碼會呼叫TreeMaker類,根據Name物件構建成一個JCIdent語法節點,如果多幾目錄,將構建成JCFieldAccess語法節點,JCFieldAccess節點可以是巢狀關係。

Javac原理剖析

Javac原理剖析

整個JCImport節點的語法樹如下:

Javac原理剖析

Import節點解析完成之後就是類的body,類包含interface、class、enum,下面是以class為例來介紹class是如何解析成一顆語法樹的。

Javac原理剖析

Javac原理剖析

第一個Token是Token。CLASS這個累的關鍵詞,接下來是一個使用者自定義的Token。IDENTIFIER,這個Token是類名。

最後解析整個classBody解析的結果儲存在list集合中,最後將會把這些子節點新增到JCClassDeca這顆class樹中。

下面的程式碼為例:

Javac原理剖析

這段程式碼對應的語法樹如下:

Javac原理剖析

上面的語法樹去掉了一些節點型別。在將這個類解析完成之後,會將這個類節點新增到這個類對應的包路徑的頂層節點中,這個頂層節點是JCCompilationUnit。JCCompilationUnit持有以package作為pid和JCClassDec1的集合,這個整個java檔案被解析完成,這顆完整的語法樹如下:

Javac原理剖析

關於語法分析的一點需要說明的是,所有語法節點的生成樹都是在TreeMaker類中完成的,TreeMaker實現了在JCTree。Factory介面中定義的所有節點的構成方法。

語義分析器

在語法書的基礎上進一步處理,例如給類新增預設的構造器函式,檢查變數在使用前是否已經初始化,將一些常量進行合併處理,檢查操作變數型別是否匹配,檢查所有的操作資料是否可達,檢查checked exception異常是否已經捕獲或者破除,解除Java語法糖等。

將在Java類中的符號輸入到符號表中主要由com。sun。tools。javac。comp。Enter類來完成,這個類主要完成以下兩個步驟:

1。將所有類中出現的符號輸入到類自身的符號表中,所有類符號、類的引數型別符號、超類符號和繼承的介面型別符號等都儲存到一個未處理的列表中。

2。將這個未處理列表中所有的類都解析到各自的類符號列表中,這個操作是在MemberEnter。complete()中完成。

在上面的Yufa類中,會新增一個無參建構函式。

接下來使用com。sun。tools。javac。comp。Attr檢查語法的合法性進行邏輯判斷:

1。變數的型別是否匹配

2。變數在使用前是狗已經初始化

3。能夠推匯出泛型方法的引數型別

4。字串常量的合併

例如:

Javac原理剖析

經過解析後,原始碼變成:

Javac原理剖析

這裡將兩個字串合併成一個字串。

在標註完成以後,由com。sun。tools。javac。comp。Flow完成資料流分析,主要完成的工作如下:

1。檢查變數的使用前時候已經被正確賦值,除了Java的原始型別,其他像String型別和物件的引用都必須在使用前先賦值

2。保證final修飾的變數不會被重複複製。

3。要確定方法的返回值型別

4。所有的CheckedException都要捕獲或者向上丟擲。

5。所有的語句都要被執行到。

語義分析的足後一個步驟是執行com。sun。tools。javac。comp。Flow,這就是在進一步對語法樹進行語義分析,如消除一些無用的程式碼;去除永不真的條件判斷;解除一些語法糖,型別的自動轉化操作。

一些例子如下:

型別轉化:

Javac原理剖析

Javac原理剖析

Javac原理剖析

for迴圈解析:

Javac原理剖析

趕快來分享關注吖