JVM-程式編譯與代碼早期(編譯期)最佳化,jvm-編譯
早期(編譯期)最佳化
一、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類中)實現。