深入理解Java虛擬機(jī)總結(jié)-編譯期優(yōu)化

注:此文是我在讀完周志明老師的深入理解Java虛擬機(jī)之后總結(jié)的一篇文章,請閱讀此書獲取更加詳細(xì)的信息.

在這篇文章中,我們會簡單介紹一下Java的編譯過程,以及在編譯過程中進(jìn)行的優(yōu)化.

編譯過程

編譯過程大致分為下面的三個(gè)過程,分別是:

  • 解析與填充符號表過程
  • 插入式注解處理器的注解處理過程
  • 分析與字節(jié)碼生成關(guān)系

它們之間的關(guān)系如下圖所示:

解析和填充符號表

1.詞法分析,語法分析:

詞法分析是將源代碼中的字符流轉(zhuǎn)變?yōu)門oken集合.

語法分析是根據(jù)Token序列構(gòu)造抽象語法樹的過程,抽象語法樹是一種用來描述程序代碼語法結(jié)構(gòu)的樹型表示方式,語法樹的每一個(gè)節(jié)點(diǎn)都代表著程序代碼中的一個(gè)語法結(jié)構(gòu),例如包,類型,修飾符,運(yùn)算符,接口,返回值甚至代碼注釋等都可以是一個(gè)語法結(jié)構(gòu).

2.填充符號表:

符號表是由一組符號地址和符號信息構(gòu)成的表格,符號表中所登記的信息在編譯的不同階段都要用到.在語義分析中,符號表所登記的內(nèi)容將用于語義檢查(如檢查一個(gè)名字的使用和原先的說明是否一致)和產(chǎn)生中間代碼,在目標(biāo)代碼生成階段,當(dāng)對符號表進(jìn)行地址分配時(shí),符號表是地址分配的依據(jù).

注解處理器

注解處理器用于處理程序中的代碼,由于注解會修改源代碼,也就是會修改抽象語法樹中的元素,所以如果在處理注解期間對語法樹進(jìn)行了修改,編譯器將回到解析及填充符號表的過程重新處理,直到所有插入式注解處理器都沒有再對語法樹進(jìn)行修改為止.

語義分析與字節(jié)碼生成

在編譯過程中,語義分析過程分為標(biāo)注檢查以及數(shù)據(jù)及控制流分析兩個(gè)步驟.

1.標(biāo)注檢查:

標(biāo)注檢查步驟檢查的內(nèi)容包括諸如變量使用前是否已被聲明,變量和賦值之間的數(shù)據(jù)類型是否能夠匹配等.

2.數(shù)據(jù)及控制流分析:

數(shù)據(jù)及控制流分析是對程序上下文邏輯更進(jìn)一步的驗(yàn)證,它可以檢查出諸如程序局部變量在使用前是否有賦值,方法的每條路徑是否都有返回值,是否所有的受查異常都被正確處理了等問題.

3.解語法糖:

Java中最常用的語法糖主要是前面提到過的泛型,變長參數(shù),自動裝箱/拆箱等,虛擬機(jī)運(yùn)行時(shí)并不支持這些語法糖,它們在編譯階段還原回簡單的基礎(chǔ)語法結(jié)構(gòu),這個(gè)過程稱為解語法糖.

4.字節(jié)碼生成:

字節(jié)碼生成階段不僅僅是把前面各個(gè)步驟生成的信息(語法樹,符號表)轉(zhuǎn)化成字節(jié)碼寫到磁盤中,編譯器還進(jìn)行了少量的代碼添加和轉(zhuǎn)換工作.

例如,之前的文章提到過的實(shí)例構(gòu)造器<init>()方法和類構(gòu)造器<clinit>()方法就是在這個(gè)階段添加到語法樹之中的.

完成對語法樹的遍歷和調(diào)整之后,就會把填充了所有所需信息的符號表交給com.sun.tools.javac.jvm.ClassWriter類,由這個(gè)類的writeClass()方法輸出字節(jié)碼,生成最終的Class文件,到此為止整個(gè)編譯過程宣告結(jié)束.

Java語法糖

泛型與類型擦除

泛型可謂是Java中最常用的幾個(gè)語法糖中的一個(gè),但是Java中的泛型和C++中的語法糖不一樣,它只存在于程序代碼中,在經(jīng)過編譯之后的Class文件中,便不再存在.

那我們是如何獲取到泛型傳入的參數(shù)化類型呢?

這就涉及到Class文件中的Signature,LocalVariableTypeTable等屬性了.其中Signature是最重要的一項(xiàng)屬性,它的作用就是存儲一個(gè)方法在字節(jié)碼層面的特征簽名,這個(gè)屬性中保存的參數(shù)類型并不是原生類型,而是包括了參數(shù)化類型的信息.修改后的虛擬機(jī)規(guī)范要求所有能識別49.0以上版本的Class文件的虛擬機(jī)都要能正確地識別Signature參數(shù).

自動裝箱,拆箱與遍歷循環(huán)

自動裝箱,拆箱在編譯之后被轉(zhuǎn)化成了對應(yīng)的包裝和還原方法,而遍歷循環(huán)則把代碼還原成了迭代器的實(shí)現(xiàn),這也是為何遍歷循環(huán)需要被遍歷的類實(shí)現(xiàn)Iterable接口的原因.變長參數(shù)呢,在調(diào)用的時(shí)候變成了一個(gè)數(shù)組類型的參數(shù).

條件編譯

Java中進(jìn)行條件編譯的方式是,使用條件為常量的if語句.在編譯時(shí),會把分支中不成立的代碼塊消除掉.

總結(jié)

可以看到,其實(shí)編譯器并沒有進(jìn)行什么優(yōu)化,而只是解語法糖,去掉不需要的代碼而已.代碼的優(yōu)化實(shí)際上主要是在運(yùn)行期完成,后面我們會寫一篇文章專門來介紹編譯器優(yōu)化.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容