詞法分析
詞法分析將原始碼的字元流轉化為標記(token)集合,單個字元是程式編寫過程的最小元素,而標記則是編譯過程的最小元素,關鍵字、變數名、字面量、運算子都可以成為編輯,如“int a+b=2”這句程式碼中包含了6個標記,分別是int、a、=、b、+、2,雖然關鍵字int由三個字元構成,但是它只是一個Token,不可再拆分。在Javac的原始碼中,詞法分析過程由com.sun.tools.javac.parser.Scanner類來實現。
語法分析
詞法分析器的作用是將Java原始檔的字元流轉變成對應的Token流。而語法分析器是將詞法分析器分的Token流元件成更加結構化的語法樹,也就是將一個個單片語裝成一句話,一個完整的語句。哪些詞語組合在一起是主語,哪些是謂語、哪些是賓語、哪些是定語等沒要做進一步區分。
語法分析是根據Token序列構造抽象語法樹的過程,抽象語法樹是一種用來描述程式程式碼語法結構的樹形表示方式,語法樹的每一個節點都代表著程式程式碼中的一個語法結構,例如包、型別、修飾符、運算子、介面、返回值甚至程式碼註釋等都可以是一個語法結構。語法分析過程由com.sun.tools.javac.parser.Parser類實現,這個階段產出的抽象語法樹由com.sun.tools.javc.tree.JCTree類表示,經過這個步驟之後,編譯器就基本不會再對原始碼檔案進行操作了,後續的操作都是建立在抽象語法樹上.
語義分析
語法分析之後,編譯器獲得了程式程式碼的抽象語法樹表示,語法樹能表示一個結構正確的源程式的抽象,但無法保證源程式是符合邏輯的。語義分析是要在語法樹的基礎上再做一些處理,如給類新增預設的建構函式,檢查變數在使用前是否已經初始化,將一些常量進行合併處理,檢查操作變數型別是否匹配,檢查所有的操作語句是否可達,檢查checked exception是否正確處理。
語義分析階段分為:填充符號表、標註檢查、資料及控制流分析。
填充符號表
符號表是由一組符號地址和符號資訊構成的表格,讀者可以把它想象成雜湊表K-V值對的形式。符號表中所登記的資訊在編譯的不同階段都要用到。在語義分析中,符號表所登記的內容將用於語義檢測和產生中間程式碼。在目的碼生成階段,當對符號名進行地址分配時,符號表是地址分配的依據。在Javac原始碼中,填充符號表的過程由com.sun.tools.javac.comp.Enter類實現。
一個類除了類本身會定義一些符號變數如類名稱、變數名稱和方法名稱等,還有一些符號是引用其它類的,這些符號會呼叫其它類的方法或者變數等,還有一些類可能會繼承或者實現超類和介面等。這些符號都是在其他類中定義的,那麼就需要將這些類的符號也解析到符號表中。
??在Enter類解析這一步驟中,還有一個重要的步驟就是新增預設的建構函式。如果程式碼中沒有提供任何建構函式,那麼編譯器將會新增一個沒有引數、訪問下與當前一致的預設建構函式。
標註檢查
檢查的內容包括諸如變數的型別是否匹配、變數在使用前是否已經初始化、能夠推匯出泛型方法的引數型別、字串常量的合併(常量摺疊)。在標註檢查步驟中一個重要的動作稱為常量摺疊,如果我們在程式碼中寫了如下定義:
int a=1+2;
那麼在語法樹上仍然能看到字面量1、2以及操作符+,但是在進過常量摺疊之後,他們將會被摺疊為字面量3.實現的類是com.sun.tools.javac.comp.Attr類和com.sun.tools.javac.comp.Check類。
資料流分析
資料流主要完成如下工作:
檢查變數在使用前是否都已經被正確賦值。保證final修飾的變數不會被重複賦值。要確定方法的返回值型別。這裡需要檢查方法的返回值型別是否確定,並檢查接受這個方法返回值的引用型別是否匹配,如果沒有返回值,則不能有任何引用型別指向方法的這個返回值。所有的Checked Exception都要捕獲或者向上丟擲。所有的語句都要被執行到。這裡會檢查是否有語句出現在一個return方法的後面,因為在return方法後面的語句永遠也不會被執行到。
控制流分析
控制流主要完成如下工作:
去掉無用的程式碼,比如永假的if程式碼塊。變數的自動轉換,比如自動裝箱拆箱。去除語法糖。解語法糖的過程由desugar()方法觸發,在com.sun.tools.javac.comp.TransTypes和com.sun.tools.javac.comp.Lower類中完成。
資料流及控制流的分析入口是flow()方法,具體操作由com.sun.tools.javac.comp.Flow類來完成。
位元組碼生成
由com.sun.tools.javac.jvm.Gen類來完成。位元組碼階段不僅僅把前面各個步驟所生成的資訊(語法樹、符號表)轉化成位元組碼寫到磁碟中,編譯器還進行了少量的程式碼新增和轉換工作。
例項構造器方法和類構造器方法就是在這個階段新增到語法樹中的。
生成java位元組碼需要經過以下兩個步驟:將java方法中的程式碼塊轉化成符合JVM語法的命令形式,JVM的操作都是基於棧的,所有的操作都必須經過出站和進展來完成。按照JVM的檔案組織格式將位元組碼輸出到以class為副檔名的檔案中。
在jdk1.5之後,java語言提供了對註解(Annotation)的支援,這些註解與普通的Java程式碼一樣,是在執行期間發揮作用的。在Jdk1.6中提供了一組插入式註解處理器的標準API在編譯期間對註解進行處理,我們可以把它看做是一組編譯器的外掛,在這些外掛裡面,可以讀取、修改、新增抽象語法樹中的任意元素。如果這些外掛在處理註解期間對語法樹進行了修改,編譯器將回到解析及填充符號表的過程重新處理,直到所有插入式註解處理器都沒有再對語法樹進行修改為止。對註解的處理是在填充符號表之後及在標註註解之前發生的。