簡介
我們知道JIT會在JVM運作過程中,對熱點代碼進行優化,傳說自然是傳說,今天我們通過一個簡單的例子來具體分析一下JIT到底是怎麼進行優化的。
一個簡單的例子
說幹就幹,我們先準備一個非常簡單的例子:
public class AddTest {
static int a = 1;
static int b = 2;
static int c = 3;
public static void main(String[] args) {
for (int i = 0; i < 100000; i++) {
add();
}
}
private static void add() {
a = b + 1;
b = c + 2;
c = a + 3;
}
}
這個例子中我們定義了三個類變量,然後通過一個add方法對其中的變量進行累加。
然後在main方法中對add方法調用10000次。調用這麼多次,主要是為了保證add成為熱點代碼,進而使用JIT進行編譯。
使用jitWatch進行分析
之前提到了JIT分析的神器jitWatch,今天我們來使用jitWatch來分析上面的代碼。
從jitWatch的github中下載下傳源碼,運作mvn exec:java即可開啟jitWatch之旅。
打開sandbox,選擇我們編寫的類檔案。點選運作即可。
然後我們到了下面熟悉的界面:
界面分為三部分,左邊是源代碼,中間是位元組碼,最右邊是JIT編譯的彙編代碼。
分析位元組碼
我們分析下add方法生成的位元組碼:
0: getstatic #13 // Field b:I
3: iconst_1
4: iadd
5: putstatic #17 // Field a:I
8: getstatic #20 // Field c:I
11: iconst_2
12: iadd
13: putstatic #13 // Field b:I
16: getstatic #17 // Field a:I
19: iconst_3
20: iadd
21: putstatic #20 // Field c:I
24: return
我們可以看到位元組碼和java源代碼是一一對應的。
比如add方法的第一行:
a = b + 1;
相應的位元組碼是這樣的:
0: getstatic #13 // Field b:I
3: iconst_1
4: iadd
5: putstatic #17 // Field a:I
首先通過getstatic拿到字段b的值。然後調用iconst_1,将1加載。接着調用iadd把1和b相加。最後将生成的值使用putstatic指派給a。
位元組碼和源代碼一一對應,完全沒有問題。
分析彙編代碼
那麼JIT生成的彙編代碼是不是也和java代碼一緻呢?我們再來看一下生成的彙編代碼。
從圖檔我們可以看出,生成的彙編代碼可以分為方法初始化,代碼邏輯區,多線程同步,位址和cache line對齊,異常處理,返優化等幾個部分。
這裡我們主要關注一下代碼邏輯區:
從圖上我做的标記可以看出,彙編中執行的邏輯是 b=c+2, a =b+1和c=b+4。
不光執行順序發送了變化(重排序),執行邏輯也進行了優化。
大家可能注意到彙編語言中有這樣幾個不太明白的代碼:
0x78(%r10)
0x74(%r10)
0x70(%r10)
通過第二行的注解,我們知道r10存儲的是AddTest這個對象,而0x70,0x74和0x78是AddTest中的偏移量,用來定位類變量a,b,c。
總結
從上面的例子可以知道,JIT會對代碼進行優化,是以最好的辦法是不要自己在java代碼中做一些你認為是優化的優化,因為這樣可能讓JIT在優化的時候變得困惑。進而限制了代碼優化的力度。
最後,JIT是一個非常強大的工具。希望大家能夠喜歡。
小編為大家整理了很多JVM的視訊資料,大家可以添加V:mm7718mm,自行擷取。