早期(編譯期)優化
一、Javac編譯器
1.Javac的源代碼與調試
Javac的源代碼放在JDK_SRC_HOME/langtools/src/shares/classes/com/sun/tools/javac中,除了JDK自身的API之外,就只引用了JDK_SRC_HOME/langtools/src/shares/classes/com/sun/*裡面的代碼,調試環境建立起來簡單方便,因為基本上不需要處理依賴關系。
編譯過程大致可以分成3個過程:
(1)解析與填充符號表過程
(2)插入式注釋處理器的注解過程處理
(3)分析與字節碼生成過程
Javac編譯動作的入口是com.sun.tools.javac.main,JavaCompikler類,上述3個過程的代碼邏輯集中在這個類的compiler()和compiler2()方法中。
2.解析與填充符號表
解析步驟包括詞法分析和語法分析兩個過程
(1)詞法、語法分析
詞法分析是將源代碼的字節流變成標記(Token)集合,單個字符是程序編碼過程的最小元素,而標記則是編譯過程的最小元素。
在Javac的源代碼中,詞法分析過程由com.sun.tools.javac.parser.Scanner類來實現。
詞法分析是根據Token序列構造抽象語法樹的過程,抽象語法是一種用來描述程序代碼語法結構的樹形表示方式,語法樹的每一個節點都代表著程序代碼中的一個語法結構。
語法分析過程由com.sun.tools.javac.parser.Parse類來實現,這個階段產生出抽象語法樹有com.sun.tools.javac.tree.JTree類表示,經過這個步驟之後,編譯器就基本不會再對源代碼文件進行操作了,後續的操作都建立在抽象語法樹上。
(2)填充符號表
符號表(Symbol Table)是由一組符號地址和符號信息構成的表格。
在語法分析中,符號表所登記的內容將用於語法分析檢查和產生中間代碼。
在目標代碼生成階段,當對符號名進行地址分配時,符號表是地址分配的依據。
在Javac源代碼中,填充符號表的過程由com.sun.tools.javac.compiler.Enter類來實現,此過程的出口是一個待處理列表(ToDoList),包含;了每一個編譯單元的抽象語法樹的頂級節點以及package-info-java的頂級節點。
3.注解處理器
在Javac源碼中,插入式注解處理器的初始化過程是在initProcessAnnotations()方法中完成的,而它的執行過程則是在ProcessAnnotations()方法中完成的。這個方法判斷是否有新的注解處理器需要執行,如果有的話,通過com.sun.tools.javac.Processing.JavacProvcessingEnviroment類的doProcessing()方法生成一個新的JavaCompiler對象對編譯的後續步驟進行處理。
4.語義分析與字節碼生成
(1)標注檢查:內容包括諸如變量使用前後是否已被聲明,變量與賦值之間的數據類型是否能夠匹配等。在標注檢查步驟中,還有一個重要的動作,稱為常量折疊。
標注檢查步驟在javac源代碼中實現類是com.sun.tools.javacComp.Attr類和com.sun.tools.javac.comp.Check類。
(2)數據及控制分析
對程序上下文邏輯更進一步的驗證,它可以檢查出諸如程序局部變量在使用前後是否有賦值,方法的每一條路徑是否都有返回值,是否所有的受檢查異常都被正確出來了等問題。
在Javac的源代碼中,數據及控制流分析的入口是flow()方法,具體操作是由com.sun.tools.javac.comp.Flow類來完成的。
(3)解語法糖
語法糖(Syntatic Sugar),也稱糖衣語法,指在計算機語言中添加的某種語法,這種語法對語言的功能並沒有影響,但是更方便使用。
在Javac的源代碼中,解語法糖的過程由的desugar()方法觸發,在com.dun.tools.javac.comp.TransTypes類和com.sun.tools.javac.comp.Lower類中完成。
(4)字節碼生成
字節碼生成是Javac編譯過程的最後一個階段,在Javac源代碼裡面有com.sun.tolls.javac.jvm.Gen類來完成。
完成了語法樹的遍歷和調整之後,就會把填充了所有需要信息的符號表交給com.sun.tolls.javac.jvm.ClassWrite類,由這個類的WiteClass()方法輸出字節碼,生成最終的class文件。到此為止整個編譯過程就結束了。
二、Java語法糖
1.泛型與類型擦除
C#裡面泛型無論在程序源碼中,編譯後的IL中,貨值運行期的CLR中,都是切實存在的,List<int>與List<string>就兩個不同稍微類型,它們在系統運行期生成,有自己的虛方法表和,類型數據,這種實現機制稱為類型膨脹,基於這種方法實現的泛型稱為真是泛型。
Java語言中的泛型不一樣,它只在程序源代碼中存在,在編譯後的字節碼文件中,就已經替換為原來的原生類型了,並且在相應的地方插入了強制轉型代碼,因此,對於運行期的Java語言來說,ArrayList<String>與ArrayList<int>就是同一個類,所以泛型技術實際上是Java語言的一顆語法糖,java語言中的泛型實現方法稱為類型擦除,基於這種方法的泛型稱為偽泛型。
虛擬機規范中引入了諸如Signature,LocalVariableType等新的屬性用於解決伴隨泛型而來的參數類型識別問題。
2.自動裝箱、拆箱與遍歷循環
自動裝箱、拆箱在編譯之後轉化成了對應的包裝盒還原方法,而遍歷循環則把代碼還原成迭代器的實現。
包裝類的“==”運算在不遇到算術符運算的情況下不會自動拆箱,以及它們equals()方法不處理數據轉型的關系。
3.條件編譯
Java語言可以進行條件編譯,方法就是使用條件為常量的if語句。
Java語言中條件編譯的實現是,Java語言的一顆語法糖,根據布爾常量的真假,編譯器將會把分支中不成立的代碼清除掉。這一項工作將在編譯器解除語法糖階段(com.sun.tools.javac.comp.Lower類中)實現。