雖然在不同目標(biāo)平臺(tái)上的JVM實(shí)現(xiàn)不一樣,但是有3種Java Compiler是極具代表性的:
- 前端編譯器: 如我們經(jīng)常使用的javac. 在編譯時(shí)將全部的java源碼文件轉(zhuǎn)變成class文件挨下,再放入目標(biāo)機(jī)器執(zhí)行
- JIT編譯器:Android Dalvik / Android ART. 在運(yùn)行時(shí)期將字節(jié)碼轉(zhuǎn)變成機(jī)器碼
- AOT Compiler: (Ahead of Time Compiler) 在編譯時(shí)就將java文件編譯成目標(biāo)平臺(tái)上的機(jī)器碼
前端編譯器
從傳統(tǒng)的javac來看熔恢,編譯過程大致分為3個(gè)階段:
- 解析與填充符號(hào)表過程
- 注解處理器的注解處理過程
- 分析與字節(jié)碼生成過程
解析與填充符號(hào)表過程
- 詞法與語法分析: 將源代碼中的字符流構(gòu)造成抽象語法樹的過程。抽象語法樹是一種用來描述程序代碼語法結(jié)構(gòu)的樹形表示方法复颈。
- 填充符號(hào)表: 符號(hào)表是一組符號(hào)地址和符號(hào)信息構(gòu)成的表格绩聘。
注解處理器處理
我們平時(shí)使用的Java注解沥割,本質(zhì)就是用來對(duì)語法樹做出修改,處理器的任務(wù)便是在編譯是識(shí)別出這些注解同時(shí)修改抽象語法樹凿菩。
語義分析與字節(jié)碼生成
- 標(biāo)注檢查:語義分析的第一個(gè)步驟机杜,用來檢查包括諸如使用的變量是否已經(jīng)聲明、變量與賦值之間的數(shù)據(jù)類型是否能夠匹配衅谷。其中有一個(gè)重要的步驟就是常量折疊
如:int a = 3 + 2椒拗, javap查看生成的class字節(jié)碼,iconst指令向操作數(shù)棧中壓入的是常量值5获黔,這樣在運(yùn)行時(shí)期就不會(huì)CPU的運(yùn)算量重新計(jì)算蚀苛。 - 數(shù)據(jù)及控制流分析
用于檢查諸如局部變量在使用前是否已經(jīng)賦值,方法的每條路徑上是否都有返回值玷氏,受查異常是否都被正常處理等問題堵未。 - 解語法糖(desugar)
Java中我們經(jīng)常使用的語法糖有泛型、自動(dòng)裝箱/拆箱盏触、遍歷循環(huán)渗蟹、條件編譯等.. 由于這些語法在運(yùn)行時(shí)無法被識(shí)別,因此需要在編譯階段就將他們還原回簡單的基礎(chǔ)語法結(jié)構(gòu)赞辩。
泛型
泛型的本質(zhì)是參數(shù)化類型的應(yīng)用雌芽,參數(shù)類型可以用在類、接口和方法的創(chuàng)建中辨嗽,分別稱為泛型類世落、泛型接口和泛型方法。
在Java中糟需,泛型只在源碼中存在屉佳,一旦經(jīng)常編譯,就會(huì)替換成原來的原生類型并且在相應(yīng)的地方加上了強(qiáng)制類型轉(zhuǎn)換代碼洲押。這一過程也叫做類型擦除
自動(dòng)裝箱/拆箱
基本類型自動(dòng)裝箱成包裝類型忘古,包裝類型拆箱成基本類型,大多數(shù)Java程序員都已經(jīng)了解诅诱。只是使用上有些地方需要注意,比方包裝類型在使用“==”進(jìn)行比較時(shí)送朱,只有遇到算術(shù)運(yùn)算時(shí)才會(huì)自動(dòng)拆箱娘荡。
遍歷循環(huán)
諸如 for(int i : list) {} 這樣的loop循環(huán)在編譯后都會(huì)轉(zhuǎn)換成對(duì)數(shù)據(jù)結(jié)構(gòu)iterator的調(diào)用
條件編譯
對(duì)于if和常量搭配使用,在編譯器發(fā)現(xiàn)不會(huì)執(zhí)行到的語句塊驶沼,將不會(huì)出現(xiàn)在編譯之后的class文件當(dāng)中 - 生成字節(jié)碼class文件
在Compiler生成class文件時(shí)炮沐,不僅僅將前面步驟生成的信息轉(zhuǎn)化成字節(jié)碼,還會(huì)進(jìn)行少量的代碼添加和轉(zhuǎn)換工作回怜,如<init>和<clinit>就是在該階段添加進(jìn)class文件當(dāng)中大年,如果Compiler發(fā)現(xiàn)代碼中沒有任何構(gòu)造器换薄,那么將會(huì)添加一個(gè)無參數(shù)的、訪問性與當(dāng)前類一致的構(gòu)造器(也即經(jīng)常所說的默認(rèn)無參構(gòu)造器)
JIT
在了解早期編譯優(yōu)化時(shí)已經(jīng)學(xué)習(xí)了傳統(tǒng)的前端編譯器翔试。那么對(duì)商用JVM來說轻要,還有一種極其重要的編譯器,這便是JIT Compiler(Just In Time Compiler)垦缅。我們知道冲泥,Java程序都是通過解釋器(interpreter)進(jìn)行解釋執(zhí)行的,而一些頻繁運(yùn)行的程序代碼便會(huì)被認(rèn)為熱點(diǎn)代碼(Hot Spot Code)壁涎。JIT的任務(wù)便是將這些熱點(diǎn)代碼編譯成目標(biāo)平臺(tái)的機(jī)器碼凡恍,并進(jìn)行各種層次的優(yōu)化。
JIT雖然能夠加快JVM的執(zhí)行效率怔球,但是卻同內(nèi)存資源有著更高的要求嚼酝,解釋器可以節(jié)約內(nèi)存,在一些資源受限的機(jī)器運(yùn)行JVM時(shí)解釋器更受青睞竟坛。不過現(xiàn)在已經(jīng)很少見到?jīng)]有JIT的虛擬機(jī)了闽巩,更多的是兩種配合使用,甚至是三種Compiler一起使用流码。如Android在5.0 以前使用的Dalvik 虛擬機(jī)就使用的是Interpreter + JIT又官,5.0 ~ 7.0 之間使用的ART 是Interpreter + AOT的組合,而在最新的7.0之后的ART上使用的是Interpreter + JIT + AOT的組合漫试。