java 常量折叠_深入理解Java虚拟机之早期编译器优化
Javac編譯器
Javac編譯器是一個由Java語言編寫的程序
Javac的源碼與調試
從Sun Javac的代碼來看,編譯器大致分為3個過程:
解析與填充符號表的過程
插入式注解處理器的注解處理過程
分析與字節碼生成的過程
Javac編譯動作的入口為com.sun.tools.javac.main.JavaCompiler類,上述3個過程的代碼邏輯集中在這個類的compile()和compile2()方法中。
解析與填充符號表
解析步驟由上圖的parseFiles()方法完成,解析步驟包括了經典程序編譯原理中的詞法分析和語法分析兩個過程。
詞法、語法分析
詞法分析是將源代碼的字符流轉變為標記(Token)集合,標記為編譯過程的最小元素。關鍵字、變量名、字面量、運算符都可以成為標記。如int a = 3;,int就是一個Token。詞法分析過程由com.sun.tools.javac.parser.Scanner類來實現。
語法分析是根據Token序列構造抽象語法樹的過程,抽象語法樹是一種用來描述程序代碼語法結構的樹形表示方式,語法樹的每一個節點都代表著程序代碼中的一個語法結構。
可以根據Eclipse AST View插件分析出代碼的抽象語法樹圖。在Javac的源碼中,語法分析過程由com.sun.tools.javac.parser.Parser類實現,這個階段產生的抽象語法樹由com.sun.tools.javac.tree.JCTree類表示,經過這個步驟后,后續的操作都建立在抽象語法樹之上。
填充符號表
完成詞法、語法分析之后就是填充符號表的過程,就是圖中enterTrees()方法。符號表是由一組符號地址和符號信息構成的表格。符號表中所登記的信息在編譯的不同階段都要用到,在語法分析中,符號表所登記內容將用于語義檢查和產生中間代碼。在目標代碼生成階段,當對符號名進行地址分配時,符號表是地址分配的依據。
注解處理器
在jdk1.5之后,Java語言提供了對注解(Annotation)的支持。在jdk1.6中,提供了一組插入式注解處理器的標準API在編譯期間對注解進行處理,其中,我們可以讀取、修改、添加抽象語法樹中的任意元素。如果這些插件在處理注解期間對語法樹進行修改,編譯器將回到解析及填充符號表的過程重新處理,直到所有插入式注解處理器都沒有再對語法樹進行修改為止,每一次循環稱為一個Round,也是上圖中的回環過程。
插入式注解處理器的初始化過程是在initPorcessAnnotations()方法中完成的,而它的執行過程則是在processAnnotations()方法中完成的,這個方法判斷是否還有新的注解處理器需要執行,如果有的話,通過com.sun.tools.javac.processing.JavacProcessingEnvironment類的doProcessing()方法生成一個新的JavaCompiler對象對編譯的后續步驟進行處理。
語義分析與字節碼生成
語法樹表示一個結構正確的源程序的抽象,但無法保證源程序是符合邏輯的。而語義分析的主要任務是對結構上正確的源程序進行上下文有關性質的審查。
標注檢查
語法分析過程分為標注檢查以及數據及控制流分析兩個步驟,為圖中的attribute()和flow()
標注檢查步驟檢查的內容包括諸如變量的使用前是否已被聲明、變量與賦值之間的數據類型是否能夠匹配等。
在標注檢查步驟中,有一個重要的動作稱為常量折疊,如果我們在代碼中定義了int a=1+2;那么在語法樹上仍能看見字面量“1”,“2”以及操作符“+”,但經過折疊后,將會被折疊為字面量“3”,所以int a=1+2;比起int a=3;并不會增加程序運行期間計算量。
標注檢查步驟在Javac源碼中的實現類為com.sun.tools.javac.comp.Attr類和com.sun.tools.comp.Check類。
數據及控制流分析
數據及控制流分析是對程序上下文邏輯更進一步的驗證,可以檢查出諸如程序局部變量在使用前是否有賦值、方法的每條路徑是否都有返回值、是否所有的受查異常都被正確處理等問題。
在Javac的源碼中,數據及控制流分析的入口為上圖中的flow()方法,具體操作由com.sun.tools.javac.comp.Flow類完成。
解語法糖
語法糖指在計算機語言中添加的某種語法,這種語法對語言的的功能并未有影響,但是更加方便程序員使用。
Java中的常用語法糖主要是前面提到過的泛型、變長參數、自動裝箱/拆箱等,虛擬機運行時不支持這些語法,它們在編譯階段還原回簡單的基礎語法結構,這個過程稱為解語法糖。
在Javac源碼中,解語法糖的過程由desugar()方法觸發,在com.sun.tools.javac.comp.TransTypes類和com.sun.tools.javac.comp.Lower類完成。
字節碼生成
字節碼生成是Javac編譯過程的最后一個階段,在Javac源碼里面由com.sun.tools.javac.jvm.Gen類完成。字節碼生成階段不僅僅是把前面的各個步驟所生成的信息轉化成字節碼寫到磁盤中,編譯器還進行了少量的代碼添加和轉換工作。
完成了對語法樹的遍歷和調整之后,就會把填充了所有所需信息的符號表交給com.sun.tools.javac.jvm.ClassWriter類,由這個類的writeClass()方法輸出字節碼,生產最終的Class文件,到此為止整個編譯過程宣告結束。
Java語法糖
泛型與類型擦除
Java語言中的泛型只在程序源碼中存在,在編譯后的字節碼文件中,就已經替換為原來的原生類型了,并且在相應的地方插入強制轉型代碼,Java語言中的泛型實現方法稱為類型擦除,基于這種方法實現的泛型稱為偽泛型。
由于Java泛型的引入,JCP組織引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來的參數類型的識別問題。Signature作用就是存儲一個方法在字節碼層面的特征簽名,這個屬性中保存的參數類型并不是原生類型,而是包括了參數化類型的信息。
從Singature中可以看出,所謂的擦除,只是對方法的Code屬性中的字節碼進行擦除,實際上元數據還是保留了泛型信息,這也是我們能通過反射手段取得參數化類型的根本依據。
自動裝箱、拆箱與遍歷循環
自動裝箱、拆箱在編譯之后被轉化成了對應的包裝盒還原方法。遍歷循環則把代碼還原成了迭代器的實現。變長參數在調用時候變成了一個數組類型的參數。
條件編譯
C、C++中是使用預處理器指示符來完成條件編譯,而在Java中沒有預處理器,因為Java天然的編譯方式(編譯器并非一個個地編譯Java文件,而是將所有編譯單元的語法樹頂級節點輸入到待處理列表后再進行編譯,因此各個文件之間能夠互相提供符號信息。)無須使用預處理器。
而Java使用條件為常量的if語句來進行條件編譯。如果使用常量與其他帶有條件判斷能力的語句搭配,則可能在控制流分析中提示錯誤,被拒絕編譯。
根據布爾常量值的真假,編譯器將會把分支中不成立的代碼塊消除掉,這一工作將在編譯器解除語法糖階段(com.sun.tool.javac.comp.Lower類中)完成,因為這種條件編譯的實現方式使用了if語句,所以只能寫在方法體內部,因此它只能實現語句基本塊級別的條件編譯。
參考《深入理解Java虛擬機》
總結
以上是生活随笔為你收集整理的java 常量折叠_深入理解Java虚拟机之早期编译器优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1115 Counting Nodes
- 下一篇: java虚拟机内存模型种类_深入理解vo