Java學(xué)習(xí)-程序編譯與代碼優(yōu)化

介紹

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文件中,將無法通過編譯,但是如果有不同的返回參數(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è)代碼將無法完成編譯

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)作,如 無用代碼消除(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 無疑是比較緩慢的,但它的便以速度仍遠(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=e
12+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)。無論如何,為了安全,數(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ì)于虛方法,編譯期間無法確定使用方法的哪個(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)量替換

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容