天天看點

JVM讀書筆記(六)——早期(編譯期)優化

6.1 概述

Java語言的編譯期實際上是一段不确定的操作過程,因為它可能是指一個編譯器的前端把*.java轉變成*.class檔案的過程,也可能是虛拟機的後端運作期的編譯器(JIT編譯器)把位元組碼轉變成機器碼的過程,還可能是指使用靜态提前編譯器直接把*.java檔案編譯成本地機器代碼的過程。

其中最符合大家對Java程式編譯認知的應是第一種,本文所将的編譯期和編譯器都僅限第一種。

6.2 Javac編譯器

6.2.1 Javac的源碼與調試

Javac的源碼存放在JDK_SRC_HOME/langtools/src/share/classes/lang/tools/javac中,除了jdk自身的api之外,就隻引用JDK_SRC_HOME/langtools/src/share/classes/com/sun/*裡面的代碼,調試環境建立起來簡單友善,因為基本上不需要處理依賴關系。

6.2.2 解析和填充符号表

1.詞法、文法分析

詞法分析是将源代碼的字元流轉化為标記集合,單個字元時程式編寫過程的最小元素,而标記則是編譯過程的最小元素。

文法分析是根據Token序列構造抽象文法樹的過程,抽象文法樹是一種用來描述程式代碼文法結構的樹形表示方法,文法樹的每一個節點都代表着程式代碼中的一個文法結構。

2.填充符号表

符号表是由一組符号位址和符号資訊構成的表格,可以想象成K-V形式。符号表中所登記的資訊在編譯的不同階段都要用到。在語義分析中,符号表所登記的内容将用于語義檢查和産生中間代碼。在目标代碼生成階段,當對符号名進行位址配置設定時,符号表示位址配置設定的依據。

6.2.3 注解處理器

JDK提供了一種插入式注解處理器的标準API在編譯期間對注解進行處理,可以把它看做是一組編譯器的插件,在這些插件裡,可以讀取、修改、添加抽象文法樹的任意元素。如果這些插件在處理注解期間對文法樹進行了修改,編譯器将回到解析及填充符号表的過程重新處理,直到所有插入式注解處理器都沒有對文法樹進行修改為止,每一次循環稱為一個round。

6.2.4 語義分析與位元組碼生成

1.标注檢查

Javac的編譯過程中,語義分析過程分為标注檢查以及資料及控制流分析兩個步驟。标注檢查的内容包括諸如變量使用前是否已被聲明、變量與資料之間的指派類型是否能夠比對等。

2.資料及控制流分析

資料及控制流分析是對程式上下文邏輯更進一步的驗證,它可以檢查諸如局部變量在使用前是否有指派、方法的每條路徑是否都有傳回值、是否所有的受查異常都被正确處理了等問題。編譯及控制流分析與類加載時的資料及控制流分析的目的基本上是一緻的,但校驗範圍有所差別,有一些校驗項隻有在編譯期或運作期才能進行。

3.解文法糖

在javac的源碼中,解文法糖的過程由desugar()觸發,在com.sun.tools.javac.comp.TransTypes類和com.sun.tools.javac.comp.Lower類中完成。

4.位元組碼生成

位元組碼生成是Javac編譯過程的最後一個階段,在Javac源碼裡面由com.sun.tools.javac.jvm.Gen類來完成。位元組碼生成階段不僅僅把前面各個步驟所生成的資訊轉化成位元組碼寫的磁盤中,編譯器還進行了少量的代碼添加和轉換工作。

6.3 Java文法糖

6.3.1 泛型與類型擦除

Java語言中的泛型隻在程式源碼中存在,在編譯後的位元組碼檔案中,就已經替換為原來的原生類型,并且在相應的地方插入了強制轉型代碼,是以,對于運作期的Java語言來說,ArrayList<List>與ArrayList<String>就是同一個類,是以泛型技術實際上是Java語言的一顆文法糖,Java語言中泛型實作的方法稱為類型擦除,基于這種方法實作的泛型稱為僞泛型。

6.3.2 自動裝箱、拆箱和周遊循環
6.3.3 條件編譯

Java語言找那個條件編譯的實作,也是Java語言的一塊文法糖,根據布爾常量值的真假,編譯器将會把分支中不成立的代碼塊消除掉,這一工作将在編譯器解除文法糖階段完成。由于這種條件編譯的實作方式使用了if語句,是以它必須遵循最基本的Java文法,隻能寫在方法内部,是以它隻能實作語句基本塊級别的條件編譯,而沒有辦法實作根據條件調整整個Java類的結構。