介紹
java代碼編譯器代表性的有三類
前端編譯器:我們熟知的javac就是前端編譯器
JIT編譯器:即時(shí)編譯器舱污,如hotspot的C1與C2編譯器也物,java的大部分優(yōu)化在這個(gè)編譯器里
AOT編譯器:這個(gè)是什么鬼夹界?
程序編譯
javac程序編譯分為三個(gè)過程:解析與填充符號(hào)表的過程,插入式注解處理器的注解處理過程司志,分析與字節(jié)碼生成過程例衍。具體未去探究
語(yǔ)法糖
語(yǔ)法糖的定義
語(yǔ)法糖是在計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能并沒有影響恍箭,但是方便程序員使用刻恭。
1.java中的語(yǔ)法糖
1.1 泛型與泛型擦除
例如
public static void method(List<String> list){
System.out.println("測(cè)試");
}
public static void method(List<Integer> list){
System.out.println("測(cè)試");
}
上述的這兩個(gè)方法具有相同的方法簽名,泛型擦除后都變成了
public static void method(List list){
System.out.println("測(cè)試");
}
所以如果最上面的兩個(gè)方法在同一個(gè)文件.java文件中扯夭,將無(wú)法通過編譯鳍贾,但是如果有不同的返回參數(shù)會(huì)通過編譯,這并不是說返回值屬于方法的特征簽名勉抓。能夠共存的原因是.class文件只要是描述符不相同的兩個(gè)方法就能共存贾漏。
1.2 自動(dòng)裝箱、拆箱藕筋、遍歷循環(huán)
自動(dòng)裝箱纵散,拆箱就不多說了。舉下面例子
Integer i=100;
這個(gè)代碼將自動(dòng)調(diào)用
Integer i = Integer.valueOf(100);
Integer i = 10; //裝箱
int t = i; //拆箱隐圾,實(shí)際上執(zhí)行了 int t = i.intValue();
遍歷循環(huán)
主要注意遍歷循環(huán)需要被遍歷的類實(shí)現(xiàn)Iterator接口伍掀。原因是在編譯后
遍歷循環(huán)把代碼還原成了迭代器的實(shí)現(xiàn)。
1.3 條件編譯
對(duì)于條件表達(dá)式中永遠(yuǎn)為false的語(yǔ)句暇藏,編譯器將不對(duì)條件覆蓋的代碼段生成字節(jié)碼蜜笤。
例如
public static void main(String[] args){
if(true){
System.out.println("one");
}else{
System.out.println("two");
}
}
這個(gè)代碼編譯后的class文件反編譯的結(jié)果
public static void main(String[] args){
System.out.println("one");
}
要注意的是只能使用條件為常量的if語(yǔ)句才能達(dá)到上述的效果
如
while(flase){
System.out.print("www");
}
這個(gè)代碼將無(wú)法完成編譯
2 后期運(yùn)行優(yōu)化
2.1 解釋器和即時(shí)編譯器
當(dāng)程序需要迅速啟動(dòng)和執(zhí)行的時(shí)候,解釋器可以首先發(fā)揮作用盐碱,省去編譯時(shí)間把兔,隨著時(shí)間的推移沪伙,即時(shí)編譯器逐漸發(fā)揮作用,把越來越多的代碼編譯成本地代碼县好,之后可以獲得更高的執(zhí)行效率围橡。
2.2 JIT編譯器編譯對(duì)象及觸發(fā)條件
編譯對(duì)象:熱點(diǎn)代碼
哪些代碼會(huì)成為熱點(diǎn)代碼?
1.被多次調(diào)用的方法體缕贡;
2.被多次調(diào)用的循環(huán)體翁授。
如何確定代碼成為熱點(diǎn)代碼?
1.基于采樣的熱點(diǎn)探測(cè):
此方法會(huì)周期性檢查各個(gè)線程的棧頂晾咪,如果發(fā)現(xiàn)某個(gè)或某些方法經(jīng)常出現(xiàn)在棧頂收擦,那么這個(gè)方法就是熱點(diǎn)方法。此方法的缺點(diǎn)是很難精確地確認(rèn)一個(gè)方法的熱度谍倦,容易受到諸如線程阻塞等因素影響塞赂。
2.基于計(jì)數(shù)器的熱點(diǎn)探測(cè):
此方法會(huì)為每個(gè)方法甚至是代碼塊建立計(jì)數(shù)器,統(tǒng)計(jì)方法的執(zhí)行次數(shù)昼蛀,如果執(zhí)行次數(shù)超過一個(gè)閥值就認(rèn)為它是熱點(diǎn)方法减途。
HotSpot 使用的是第二種-基于技術(shù)其的熱點(diǎn)探測(cè),并且有兩類計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(Invocation Counter )和回邊計(jì)數(shù)器
2.3 編譯過程
對(duì)于 Client 模式而言
它是一個(gè)簡(jiǎn)單快速的三段式編譯器曹洽,主要關(guān)注點(diǎn)在于局部的優(yōu)化,放棄了許多耗時(shí)較長(zhǎng)的全局優(yōu)化手段辽剧。
第一階段送淆,一個(gè)平臺(tái)獨(dú)立的前端將字節(jié)碼構(gòu)造成一種高級(jí)中間代碼表示(High-Level Intermediate Representaion , HIR)怕轿。在此之前偷崩,編譯器會(huì)在字節(jié)碼上完成一部分基礎(chǔ)優(yōu)化,如 方法內(nèi)聯(lián)撞羽,常量傳播等優(yōu)化阐斜。
第二階段,一個(gè)平臺(tái)相關(guān)的后端從 HIR 中產(chǎn)生低級(jí)中間代碼表示(Low-Level Intermediate Representation 诀紊,LIR)谒出,而在此之前會(huì)在 HIR 上完成另外一些優(yōu)化,如空值檢查消除邻奠,范圍檢查消除等笤喳,讓HIR 更為高效。
第三階段碌宴,在平臺(tái)相關(guān)的后端使用線性掃描算法(Linear Scan Register Allocation)在 LIR 上分配寄存器杀狡,做窺孔(Peephole)優(yōu)化,然后產(chǎn)生機(jī)器碼
對(duì)于 Server Compiler 模式而言
它是專門面向服務(wù)端的典型應(yīng)用贰镣,并為服務(wù)端的性能配置特別調(diào)整過的編譯器呜象,也是一個(gè)充分優(yōu)化過的高級(jí)編譯器膳凝,幾乎能達(dá)到 GNU C++ 編譯器使用-O2 參數(shù)時(shí)的優(yōu)化強(qiáng)度,它會(huì)執(zhí)行所有的經(jīng)典的優(yōu)化動(dòng)作恭陡,如 無(wú)用代碼消除(Dead Code Elimination)蹬音、循環(huán)展開(Loop Unrolling)、循環(huán)表達(dá)式外提(Loop Expression Hoisting)子姜、消除公共子表達(dá)式(Common Subexpression Elimination)祟绊、常量傳播(Constant Propagation)、基本塊沖排序(Basic Block Reordering)等哥捕,還會(huì)實(shí)施一些與 Java 語(yǔ)言特性密切相關(guān)的優(yōu)化技術(shù)牧抽,如范圍檢查消除(Range Check Elimination)、空值檢查消除(Null Check Elimination 遥赚,不過并非所有的空值檢查消除都是依賴編譯器優(yōu)化的扬舒,有一些是在代碼運(yùn)行過程中自動(dòng)優(yōu)化 了)等。另外凫佛,還可能根據(jù)解釋器或Client Compiler 提供的性能監(jiān)控信息讲坎,進(jìn)行一些不穩(wěn)定的激進(jìn)優(yōu)化,如 守護(hù)內(nèi)聯(lián)(Guarded Inlining)愧薛、分支頻率預(yù)測(cè)(Branch Frequency Prediction)等晨炕。
Server Compiler 編譯器可以充分利用某些處理器架構(gòu),如(RISC)上的大寄存器集合毫炉。從即時(shí)編譯的角度來看瓮栗, Server Compiler 無(wú)疑是比較緩慢的,但它的便以速度仍遠(yuǎn)遠(yuǎn)超過傳統(tǒng)的靜態(tài)優(yōu)化編譯器瞄勾,而且它相對(duì)于 Client Compiler編譯輸出的代碼質(zhì)量有所提高费奸,可以減少本地代碼的執(zhí)行時(shí)間,從而抵消了額外的編譯時(shí)間開銷进陡,所以也有很多非服務(wù)端的應(yīng)用選擇使用 Server 模式的虛擬機(jī)運(yùn)行愿阐。
2.4 編譯優(yōu)化技術(shù)
2.4.1 公共子表達(dá)式消除
如 int d=(cb)12+a +(a+bc)
變成 int d=e12+a+(a+e),經(jīng)過代數(shù)化簡(jiǎn)后int d=e13+a2
2.4.2 數(shù)組邊界檢查消除
在虛擬機(jī)的執(zhí)行子系統(tǒng)中趾疚,每次數(shù)組元素的讀寫都帶有一次隱含的條件判定操作缨历。對(duì)于擁有大量數(shù)組訪問的代碼,這也是一種性能負(fù)擔(dān)盗蟆。無(wú)論如何戈二,為了安全,數(shù)組的邊界檢查是必須要做的喳资,但是數(shù)組邊界檢查是不是必須在運(yùn)行期間一次不漏的檢查則是可以“商量”的事情觉吭。
如foo[3] 只要在編譯期根據(jù)數(shù)據(jù)流分析來確定foo.length的值,并判斷下標(biāo)3沒有越界仆邓,執(zhí)行的時(shí)候就不需要判斷了鲜滩。
還有如果編譯器能通過數(shù)據(jù)流分析判定循環(huán)變量的取值范圍永遠(yuǎn)在[0,foo.length)之間伴鳖,那么整個(gè)循環(huán)就可以把數(shù)組的上下界檢查消除。
2.4.3 方法內(nèi)聯(lián)
存在的問題
對(duì)于虛方法徙硅,編譯期間無(wú)法確定使用方法的哪個(gè)版本
解決方案
類型繼承關(guān)系分析(CHA)
如果是非虛方法榜聂,則直接進(jìn)行內(nèi)聯(lián)即可。如果CHA查詢出來的結(jié)果有多個(gè)版本的目標(biāo)方法嗓蘑,則通過內(nèi)聯(lián)緩存做最后一次努力须肆。
內(nèi)聯(lián)緩存工作原理
在未發(fā)生方法調(diào)用之前,內(nèi)聯(lián)緩存狀態(tài)是空的桩皿,當(dāng)?shù)谝淮握{(diào)用發(fā)生時(shí)豌汇,緩存記錄下方法的接受者版本信息,并且每次運(yùn)行方法調(diào)用都比較接受者的版本泄隔,如果一致拒贱,內(nèi)聯(lián)可以一致使用下去,如果發(fā)現(xiàn)不一致就要取消內(nèi)聯(lián)查找虛方法表進(jìn)行方法分派佛嬉。
2.4.4 逃逸分析(不成熟)
棧上分配逻澳,同步消除(鎖消除),標(biāo)量替換