本文部分摘自《深入理解 Java 虛擬機第三版》
概述
前端編譯器(也叫編譯器的前端)中的前端是指把 Java 檔案轉變為 Class 位元組碼檔案的程序,顧名思義,前端編譯器就是完成這一部分編譯作業的,
前端編譯器對代碼的運行效率幾乎沒有任何優化措施可言,Java 虛擬機設計團隊選擇把對性能的優化全部集中到運行期的即時編譯器中,這樣可以讓那些不是由 Javac 產生的 Class 檔案也能享受到編譯器優化措施所帶來的性能紅利,
前端編譯器的優化主要集中在提高程式員的編碼效率上,例如語法糖就是靠前端編譯器實作的,而非依賴位元組碼或者 Java 虛擬機的底層改進來進行,
Javac 編譯器
Javac 編譯器是使用 Java 語言撰寫的程式,從 Javac 代碼的總體結構來看,編譯程序大致可以分為一個準備程序和三個處理程序,分別如下:
- 準備程序:初始化插入式注解處理器
- 決議與填充符號表程序,包括:
- 詞法、語法分析,將源代碼的字符流轉變為標記集合,構造出抽象語法樹
- 填充符號表,產生符號地址和符號資訊
- 插入式注解處理器的進行注解處理
- 分析與位元組碼生成程序,包括:
- 標注檢查,對語法的靜態資訊進行檢查
- 資料流及控制流分析,對程式動態程序進行檢查
- 解語法糖,將簡化代碼撰寫的語法糖還原為原有形式
- 位元組碼生成,將前面各個步驟所生成的資訊轉化為位元組碼
上述的三個處理程序,執行插入式注解時又可能會產生新的符號,如果有新的符號產生,就必須轉回之前的決議、填充程序表的程序,重新處理這些新符號,因此,從總體來看,三者之間的關系與互動順序如圖:

決議與填充符號表
1. 詞法、語法分析
詞法分析是將源代碼的字符流轉變為標記(Token)集合的程序,單個字符是程式撰寫時的最小元素,而標記則是編譯時的最小元素,例如 int a = b + 2 這句代碼就包含了 6 個標記,分別是 int、a、=、b、+、2,雖然關鍵字 int 由 3 個字符構成,但它是一個獨立的標記,
語法分析是根據標記序列構造抽象語法樹的程序,抽象語法樹(Abstract Syntax Tree)是一種用來描述程式代碼語法結構的樹形表示方式,抽象語法樹的每一個節點都代表著程式代碼中的一個語法結構(Syntax Construct),例如包、型別、修飾符、運算子、介面、回傳值甚至代碼注釋等都可以是一種特定的語法結構,
經過詞法和語法分析生成語法樹后,編譯器就不會再對原始碼字符流進行操作了,后續的操作都建立在抽象語法樹上,
2. 填充符號表
完成語法分析和詞法分析之后,下一個階段是對符號表進行填充的程序了,符號表(Symbol Table)由一組符號地址和符號資訊構成的資料結構,可以類比成哈希表(不一定是哈希表實作),符號表中所登記的內容在編譯的不同階段都會用到,譬如在語法分析的程序中,符號表所登記的內容將用于語意檢查(如檢查一個名字的使用和原先的宣告是否一致)和產生中間代碼,在目標代碼生成階段,當對符號名進行地址分配時,符號表是地址分配的直接依據,
注解處理器
JDK5 之后,Java 語言提供了對注解的支持,注解原本只在運行期發揮作用,但在 JDK6 中又提供了“插入式注解處理器的”的標準 API,可以提前至編譯期對代碼中的特定注解進行處理,從而影響前端編譯器的作業程序,插入式注解處理器可以讀取、修改、添加抽象語法樹中的任意元素,如果語法樹被修改,編譯器將回到決議及填充符號表的程序重新處理,直到所有插入式注解處理器都沒有再對語法樹進行修改為止,
語意分析與位元組碼生成
經過之前的步驟后,編譯器獲得了程式代碼的抽象語法樹表示,抽象語法樹能夠表示一個結構正確的源程式,但無法保證源程式的語意是符合邏輯的,而語意分析的主要任務則是對結構上正確的源程式進行背景關系相關性質的檢查,譬如型別檢查、控制流檢查、資料流檢查等等,我們編碼時經常能在 IDE 中看到有紅線標注的錯誤提示,其中絕大多數都是來源于語意分析階段的檢查結果,
1. 標注檢查
標記檢查步驟要檢查的內容包括諸如變數使用前是否已被宣告、變數與賦值之間的資料型別是否能匹配等等,在標注檢查中,還會順便進行一個稱為常量折疊(Constant Folding)的代碼優化,如果我們寫了如下 Java 代碼:int a = 1 + 2,則在抽象語法樹上仍然能看到字面量 1、2 和運算子 + 號,但經過常量折疊優化后,它們會被折疊為常量 3,因此在代碼里定義 int a = 1 + 2 并不會比 a = 3 的效率來得更高,
2. 資料及控制流分析
資料流分析和控制流分析是對程式背景關系邏輯更進一步的驗證,它可以檢查出諸如程式區域變數在使用前是否有賦值、方法的每條路徑是否都有回傳值、是否所有的受查例外都被正確處理等,編譯時期的資料及控制流分析于類加載時的資料及控制流分析的目的基本一致,但有一些校驗項只有在編譯器或運行期才能進行,
3. 解語法糖
語法糖(Syntactic Sugar)指的是在計算機語言中添加的某種語法,這種語法對語言的編譯結果和功能并沒有實際影響,但卻能更方便程式員使用該語言,簡單來說,使用語法糖能減少代碼量、增加程式的可讀性,從而減少程式代碼出錯的機會,Java 中常見的語法糖有泛型、變長引數、自動裝箱拆箱等等
4. 位元組碼生成
位元組碼生成是 Javac 編譯程序的最后一個階段,這個階段不僅把前面各個步驟所生成的資訊(語法樹、符號表)轉化成位元組碼指令寫到磁盤中,編譯器還進行了少量的代碼添加和轉換作業,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/256613.html
標籤:Java
