概述
Java語言的「編譯期」其實是一段「不確定」的操作過程,因為他可能是指一個前端編譯器把java文件轉變成class文件的過程;也可能是指虛擬機的後端運行期編譯器把字節碼轉變成機器碼的過程;還可能是指使用靜態提前編譯器直接把java文件編譯成本地機器代碼的過程。以下是這三類比較有代表性的編譯器:
前端編譯器:Sun的javac、Eclipse JDT中的增量式編譯器。
JIT編譯器:HotSpot VM的C1、C2編譯器。
AOT編譯器:GNU Compiler for the Java (GCJ)、Excelsior JET。
Javac這類編譯器對代碼的運行效率幾乎沒有任何優化措施。虛擬機設計團隊把對性能的優化集中到了後端的即時編譯器中,這樣可以讓那些不是由Javac產生的Class文件(如JRuby、Groovy等語言的Class文件)也同樣能享受到編譯器優化所帶來的好處。但是,相當多新生的Java語法特性,都是靠編譯器的「語法糖」來實現,而不是依賴虛擬機的底層改進來支持,可以說,Java中即時編譯器在運行期的優化過程對於程序運行來說更重要,而前端編譯器在編譯器的優化過程對於程序編碼來說關係更加密切。
Javac編譯器
Javac本身就是一個由Java語言編寫的程序,這為純Java的程式設計師了解它的編譯過程帶來了很大的便利。
Javac的源碼與調試
從Sun Javac的代碼來看,編譯過程大致可以分為3個過程,分別是:
解析與填充符號表過程。
插入式註解處理器的註解處理過程。
分析與字節碼生成過程。
解析與填充符號表
解析步驟包括了經典程序編譯原理中的詞法分析和語法分析兩個過程。
1.詞法、語法分析
詞法分析是將原始碼的字符流轉變為標記集合,單個字符是程序編寫過程的最小元素,而標記則是編譯過程的最小元素。在Javac的源碼中,詞法分析過程由com.sun.tools.javac.parser.Scanner類來實現。
語法分析是根據Token序列構造抽象語法樹的過程,抽象語法樹是一種用來描述程序代碼與法結構的樹形表示方式,語法樹的每一個節點都代表著程序代碼中的一個語法結構。語法分析過程由com.sun.tools.javac.parser.Parser類實現。
2.填充符號表
完成了語法分析和詞法分析之後,下一步就是填充符號表的過程。符號表中所登記的信息在編譯的不同階段都要用到。在語義分析中,符號表所登記的內容將用於語義檢查和產生中間代碼。在目標代碼生成階段,當對符號名進行地址分配時,符號表是地址分配的依據。在Javac原始碼中,填充符號表的過程由com.sun.tools.javac.comp.Enter類實現。
註解處理器
在JDK1.5之後,Java語言提供了對註解的支持,這些註解與普通的Java代碼一樣,是在運行期間發揮作用的。
語義分析與字節碼生成
語義分析的主要任務是對結構上正確的源程序進行上下文有關性質的審查,如進行類型審查。
1.標註檢查
語義分析過程分為標註檢查以及數據及控制流分析兩個步驟。標註檢查步驟檢查的內容包括諸如變量使用前是否已被聲明、變量與賦值之間的數據類型是否能夠匹配等。
2.數據及控制流分析
數據及控制流分析是對程序上下文邏輯更進一步的驗證,他可以檢查出諸如程序局部變量在使用前是否有賦值、方法的每條路徑是否都有返回值、是否所有的受查異常都被正確處理了等問題。將局部變量聲明為final,對運行期是沒有影響的,變量的不變性僅僅由編譯器在編譯期間保障。
3.解語法糖
語法糖也稱糖衣語法,指在計算機語言中添加的某種語法,這種語法對語言的功能並沒有影響,但是更方便程式設計師使用。通常來說,使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的機會。
Java中最常用的語法糖主要是前面提到過的泛型、變長參數、自動裝箱、拆箱等,虛擬機運行時不支持這些語法,它們在編譯階段還原回簡單的基礎與法結構,這個過程稱為解語法糖。
4.字節碼生成
字節碼生成是Javac編譯過程的最後一個階段,在Javac源碼裡面由com.sum.tools.javac.jvm.Gen類來完成。字節碼生成極端不僅僅是把前面各個步驟所生成的信息(語法樹、符號表)轉化成字節碼寫到磁碟中,編譯器還進行了少量的代碼添加和轉換工作。
Java語法糖的味道
幾乎各種語言或多或少都提供過一些語法糖來方便程式設計師的代碼開發,這些語法糖雖然不會提供實質性的功能改進,但是它們或能夠提高效率,或能提醒語法的嚴謹性,或能減少編碼出錯的機會。
泛型與類型擦除
自動裝箱、拆箱與遍歷循環
條件編譯