
點贊關(guān)注,不再迷路,你的支持對我意義重大!
?? Hi,我是丑丑。本文 「Java 路線」| 導(dǎo)讀 —— 他山之石,可以攻玉 已收錄,這里有 Android 進(jìn)階成長路線筆記 & 博客,歡迎跟著彭丑丑一起成長。(聯(lián)系方式在 GitHub)
前言
- 經(jīng)過前面幾篇文章的積累,相信你已經(jīng)掌握了 靜態(tài)的 Class 文件的結(jié)構(gòu),也理解了虛擬機(jī)類加載和字節(jié)碼執(zhí)行的 動態(tài)過程;
- 這篇文章,我們來聊一聊
Java的編譯過程,你將看到從源碼到字節(jié)碼再到本地代碼的整個過程。請點贊,你的點贊和關(guān)注真的對我非常重要!
目錄

1. 經(jīng)典程序編譯原理
將源代碼翻譯為目標(biāo)代碼的過程,稱為編譯過程。在一般上下文語境,編譯一詞通常指的是*.java轉(zhuǎn)換為*.class的過程,這個過程也被稱為 編譯前端。除此之外,編譯一詞還可以指運(yùn)行期即時編譯(JIT,Just in Time Compile)或者(靜態(tài)的)提前編譯(AOT,Ahead of Time Compile),這兩種編譯稱為 編譯后端。
狹義的編譯過程是將源代碼翻譯為中間代碼的過程,例如*.c文件編譯生成*.obj文件的過程,或者*.java文件編譯生成*.class文件的過程;
廣義的編譯過程是將源代碼翻譯為機(jī)器代碼的過程,還包括*.obj文件鏈接為可執(zhí)行的*.exe文件的過程,或者*.class解釋 / 編譯為機(jī)器代碼的過程。

下面,我來整理一下 編譯前端 & 編譯后端 的要點:
2. 編譯前端
編譯前端階段中,最重要的一個編譯器就是javac 編譯器, 在命令行執(zhí)行javac命令,其實本質(zhì)是運(yùn)行了javac.exe這個應(yīng)用。Android工程師可能對于 Gradle構(gòu)建過程更為熟悉,構(gòu)建過程中有一個Task:compileDebugJavaWithJavac,其實也用到了javac 編譯器,編譯中間產(chǎn)物路徑在build/intermediates/javac/debug/classes。
2.1 javac 編譯
下面,我們整理javac 編譯器的主要處理過程:

延伸文章:
- 關(guān)于注解處理器,請閱讀:《Java | 注解處理器原理解析與實踐》
- 關(guān)于類加載,請閱讀:《Java | 談?wù)勀銓︻惣虞d過程的理解》
- 關(guān)于方法調(diào)用,請閱讀:《Java | 深入理解方法調(diào)用的本質(zhì)(含重載與重寫區(qū)別)》
2.2 Java 常用語法糖
語法糖 指的是高級語言中的某種語法,這些語法糖在編譯時進(jìn)行解語法糖,轉(zhuǎn)換為無糖語法。這些語法糖大多都是靠編譯器實現(xiàn),而不是依賴字節(jié)碼或者虛擬機(jī)的底層支持。下表總結(jié)了部分熟知的語法糖:

延伸文章:
- 關(guān)于泛型,請閱讀:《Java | 關(guān)于泛型能問的都在這里了(含Kotlin)》
- 關(guān)于內(nèi)部類,請閱讀:《Java | 為什么非靜態(tài)內(nèi)部類會持有外部類的引用》
- 關(guān)于switch,請閱讀:《Java | switch 和 if-else 哪個更高效》
3. 編譯后端
在編譯后端階段中,最重要的是 運(yùn)行期即時編譯器(JIT,Just in Time Compiler)和 靜態(tài)的提前編譯器(AOT,Ahead of Time Compiler)。
3.1 解釋執(zhí)行 & 編譯執(zhí)行
根據(jù)前面的內(nèi)容,我們知道編譯前端的核心編譯產(chǎn)物是:Class 文件。但是對于CPU來說,它是不認(rèn)得字節(jié)碼的。在《Java | 為什么 Java 實現(xiàn)了平臺無關(guān)性》這篇文章里,我們強(qiáng)調(diào)了:每種CPU只能“讀懂”自身支持的機(jī)器語言或者本地代碼(native code)。
因此,Java 虛擬機(jī)在執(zhí)行字節(jié)碼時,需要將字節(jié)碼翻譯為當(dāng)前平臺的本地代碼,可以分為:解釋執(zhí)行 & 編譯執(zhí)行,具體如下:

需要注意的是,并不是所有的Java 虛擬機(jī)都采用解釋器與編譯器并存的運(yùn)行架構(gòu)。當(dāng)觸發(fā)即時編譯時,執(zhí)行引擎(默認(rèn))不會等待即時編譯完成,而是先按解釋執(zhí)行的方式繼續(xù)執(zhí)行。直到即時編譯完成后,方法的入口地址才會被修改。
3.2 即時編譯器熱點探測
那么,即時編譯器是如何探測熱點代碼的呢?具體來說,探測的熱點代碼有兩種:被多次調(diào)用的方法 & 被多次執(zhí)行的循環(huán)體。使用的探測方法有:基于采樣 & 基于計數(shù)器:

3.3 編譯器優(yōu)化技術(shù)
Editting...
4. 總結(jié)
編譯前端的優(yōu)化措施主要目的是降低程序員的編碼復(fù)雜度,提高編碼效率。語法糖并不是不是依賴字節(jié)碼或者虛擬機(jī)的底層支持,在編譯后會轉(zhuǎn)換為基礎(chǔ)的無糖語法。另外,注解處理器相當(dāng)于編譯器的插件,開發(fā)人員可以通過它對前端編譯施加影響。
編譯后端的優(yōu)化措施主要目的是生成更高效的機(jī)器代碼,提高運(yùn)行效率。提前編譯在程序運(yùn)行前編譯字節(jié)碼,相當(dāng)于提前預(yù)熱。而即時編譯器在運(yùn)行時動態(tài)監(jiān)控程序運(yùn)行,探測得熱點代碼后編譯為本地代碼,生成的代碼更高效,但需要預(yù)熱期。
參考資料
- 《深入理解Java虛擬機(jī)(第3版本)》(第10、11章)—— 周志明 著
- 《深入理解Android:Java虛擬機(jī) ART》 —— 鄧凡平 著
- 《深入理解 JVM 字節(jié)碼》(第4、5章)—— 張亞 著
創(chuàng)作不易,你的「三連」是丑丑最大的動力,我們下次見!
