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