[四] java虛擬機JVM編譯器編譯代碼簡介 字節碼指令實例 代碼到底編譯成了什麼形式

 

前言簡介

 
前文已經對虛擬機進行過了簡單的介紹,而且也對class文件結構,以及字節碼指令進行了詳盡的說明
想要了解JVM的運行機制,以及如何優化你的代碼,你還須要瞭解一下,java編譯器究竟是如何編譯你的代碼的
本文不是從最底層的編譯原理講解
本文是針對java代碼,去查看概括總結編譯器的結果行爲,從而直觀的感覺到字節碼指令集
也就是說本文的內容,主要針對的是使用javap 查看字節碼文件中方法的code屬性中的字節碼內容
讓你從java代碼  class文件格式,以及字節碼指令集 進行一個直觀的演示
 
提醒:
若是你對字節碼指令不瞭解,並且,沒有看過前面的文章,本文可能會輕度不適.
本文示例只是爲了展現
您應該常常查看你本身的代碼的class文件去發現其中的規律
 
一條普通的指令格式
<index> <opcode> [ <operand1> [ <operand2>... ]] [<comment>]
index 表示偏移量 行號  等
opcode 表示操做碼
operandX表示操做數
comment 爲註釋
好比下圖所示
行號 0 , 操做碼 getstatic ,操做數 #24  註釋爲 Field java/lang/System..................
image_5b879224_252b_thumb[1]

其中 index  行號/偏移量  能夠做爲控制跳轉指令的跳轉目標  好比 goto  8 表示跳轉到索引爲8的指令上

還有一點須要注意的是,javap查看到的內容,你能夠認爲是class文件表述的信息,可是毫不能理解爲就是class文件中的內容
好比,class文件中沒有操做碼的助記符,好比,getstatic ,都是指令的二進制值
再好比剛纔說到的,跳轉到指定行號,對於控制轉移指令,實際的操做數是在當前指令的操做碼集合中的地址偏移量
並非那個8
只不過javap工具按照更適合咱們閱讀的方式進行了翻譯

加載存儲與算數指令

public static void main(String[] args) {

int i = -1;
int j = 3;
int k = 5;
int l = 127;
int m = 32767;
int n = 32768;


int add = i+j;
int sub = i-j;
int mul = j*k;
int div = j/k;
int rem = k%j;
int neg = ~j;
int inc = i++;

}

 

image_5b879224_2d18_thumb[1]


-1 ~ 5 使用const加載到操做數棧 其中-1 使用iconst_m1
-128~127 使用bipush -32768~32767使用sipush
其他常量池ldc
store從操做數棧保存到局部變量表
load加載局部變量到操做數棧
0. 常量-1 加載到操做數棧
1. 操做數棧保存到1號局部變量表 也就是 i = -1;
2. 常量 3 加載到操做數棧      
3. 操做數棧保存到2號局部變量表 也就是j = 3;
4. 常量 5 加載到操做數棧  
5. 操做數棧保存到3號局部變量表 也就是k =5;
6. 常量 127 加載到操做數棧
8. 操做數棧保存到4號局部變量表 也就是l = 127;
10.常量 32767 加載到操做數棧
13.操做數棧保存到5號局部變量表 也就是m = 32767;
15.加載#17號常量池數據到操做數棧
17. 操做數棧保存到6號局部變量表 也就是n = 32768;
image_5b879224_4b5a_thumb[1]

19. 加載1號局部變量到操做數棧 對應 i
20. 加載2號局部變量到操做數棧  對應 j
21. 執行iadd指令計算並將結果壓入棧頂   對應 i+j;
22. 保存棧頂元素到7號局部變量
24. 加載1號局部變量到操做數棧 對應 i
25. 加載2號局部變量到操做數棧  對應 j
26.執行isub指令計算並將結果壓入棧頂   對應i-j;
27. 保存棧頂元素減法結果到8號局部變量
29,30 加載 2號和3號局部變量到操做數棧 也就是j   k
31  執行imul指令並將結果壓棧 j*k
32 保存棧頂元素乘法結果到9號局部變量
34.35 加載 2號和3號局部變量到操做數棧 也就是j   k
36 執行idiv 結果壓入棧頂
37保存idiv結果到10號局部變量
39.40 加載3號 和 2號 也就是k   j
41 執行求餘irem 結果壓入棧頂
42 棧頂元素結果保存到11號局部變量
44加載2號局部變量  對應 j 到操做數棧
45 加載常量-1到操做數棧
46 執行異或運算結果壓入棧頂  (~x = -1 ^ x;)
47棧頂結果保存到12號局部變量
49 加載1號局部變量 對應 i
50 執行增量 1 計算 結果壓入棧頂
53 棧頂結果保存到13號變量
55 void方法 return返回

類型轉換指令

 

public static void main(String[] args) {
boolean bNum = true;

char cNum = 2;
byte byteNum = 127;
short sNum = 32767;
int iNum = 100;
long lNum = 65536;
float fNum = 2.5f;
double dNum = 6.8;

char c1 = (char)byteNum; char c2 = (char)sNum; char c3 = (char)iNum; char c4 = (char)lNum; char c5 = (char)fNum; char c6 = (char)dNum; byte b1 = (byte)cNum; byte b2 = (byte)sNum; byte b3 = (byte)iNum; byte b4 = (byte)lNum; byte b5 = (byte)fNum; byte b6 = (byte)dNum; short s1 = (short)cNum; short s2 = (short)byteNum; short s3 = (short)iNum; short s4 = (short)lNum; short s5 = (short)fNum; short s6 = (short)dNum; int i1 = (int)cNum; int i2 = (int)byteNum; int i3 = (int)sNum; int i4 = (int)lNum; int i5 = (int)fNum; int i6 = (int)dNum; long l1 = (long)byteNum; long l2 = (long)cNum; long l3 = (long)sNum; long l4 = (long)iNum; long l5 = (long)fNum; long l6 = (long)dNum; float f1 = (float)byteNum; float f2 = (float)cNum; float f3 = (float)sNum; float f4 = (float)iNum; float f5 = (float)lNum; float f6 = (float)dNum; double d1 = (double)byteNum; double d2 = (double)cNum; double d3 = (double)sNum; double d4 = (double)iNum; double d5 = (double)lNum; double d6 = (double)fNum; }

 

 
javap解析後的內容太長,接下來分段解析

數據的加載與存儲


image_5b879224_1328_thumb[1]

從數據的存儲能夠看得出來 boolean內部使用的是數值1  也就是1 表示true

數據類型轉換爲char類型


char byte short int  內部形式均爲int  因此轉換爲char是,使用的全都是 i2c
long  float double 先轉換爲int(l2i f2i d2i) 而後在統一使用 i2c 轉換爲char
image_5b879225_45f1_thumb[1]


數據類型轉換爲byte 類型


char byte short int  內部形式均爲int  因此轉換爲byte時,使用的全都是 i2b
long  float double 先轉換爲int(l2i f2i d2i) 而後在統一使用 i2b 轉換爲  byte
image_5b879225_7927_thumb[1]

數據類型轉換爲short 類型


仍是一樣的道理, char byte short int  內部形式均爲int  因此轉換爲short 使用的是   i2s
long  float double 先轉換爲int(l2i f2i d2i) 而後在統一使用 i2s 轉換爲  short
image_5b879225_7e40_thumb[1]

數據類型轉換爲int 類型

char byte short內部都是int類型.將他們轉換爲int時,不須要進行轉換
以下圖所示,一個load 對應一個store
long  float double    (l2i f2i d2i)   轉換爲int
image_5b879225_4c4a_thumb[1]

數據類型轉換爲long 類型

char byte short  int   內部都是int類型.將他們轉換爲long 時,使用  i2l
float double   轉換爲long   f2l d2l
image_5b879225_209d_thumb[1]


數據類型轉換爲float 類型

char byte short  int   內部都是int類型.將他們轉換爲float 時,使用  i2f
long double   轉換爲float     l2f  d2f
image_5b879225_2e9e_thumb[1]
 

數據類型轉換爲double 類型

 
char byte short  int   內部都是int類型.將他們轉換爲double 時,使用  i2d
 
long  
float   轉換爲double     l2d  f2d

image_5b879225_4660_thumb[1]
   
 
 

類相關指令


class Super{
}

class Sub extends Super{
}

new Object();
new Super();
Super s = new Super();
new Double(1.5);
new Sub();
Sub sub = new Sub();

 

image_5b879225_51b9_thumb[1]

new Object();
new Super();
沒有賦值給局部變量 僅僅是建立對象  調用new以後,堆中對象的引用保存在棧頂
而後調用構造方法invokespecial
 
Super s = new Super();
同上面的,須要調用new 
由於還須要保存到局部變量
因此new以後 先copy一個,也就是dup
而後調用構造方法 invokespecial
而後從操做數棧保存到局部變量 store

Super super1 = new Super();
Sub sub = new Sub();

//父類引用能夠指向子類
//子類引用不能指向父類
//可是對於指向子類的父類引用 能夠經過類型轉換爲子類
Super subToSuper = sub;
Sub superToSub = (Sub) subToSuper;

 

image_5b879225_2fff_thumb[1]

0 建立Spper
3 複製
4 調用構造方法
7 保存到1號局部變量
8 建立Sub
11 複製
12調用構造方法
15 保存到2號局部變量
16 2號加載到操做數棧
17保存到3號局部變量
18加載3號局部變量到棧
19 checkcast 進行校驗確認是否能夠轉換爲指定類型 不然報錯拋 classCastException
22 再次保存到局部變量


控制轉移指令

void intWhile() {
int i = 0;
while (i < 100) {
i++;
}
}

void intDoWhile() {
int i = 0;
do {
i++;
}
while (i < 100);
}

void intFor() {
int j = 0;
for(int i =0;i<100;i++) {
j++;
}
}

 

 

image_5b879225_6bae_thumb[1]

intWhile()方法
0. 加載常量0 到操做數棧
1.保存操做數棧元素到1號局部變量 i= 0;
2.直接跳轉到第8行
8.1號局部變量加載到操做數棧 也就是i 做爲第一個元素
9.加載常量100到操做數棧 也就是100做爲第二個元素
11.比較大小,若是前者小於後者 也就是若是 i <100 知足 跳轉到第5行  不然順序執行到14 return
5.給1號局部變量以增量1 增長
而後 8-->9-->11-->5-->8-->9-->11......往復循環 直到條件不知足,從11 跳轉到14 結束

intDoWhile()
0.加載常量0到操做數棧
1.保存常量0 到1號局部變量
2.給1號局部變量以增量1 進行自增
5.1號局部變量加載到操做數棧
6.常量100加載到操做數棧
8,比較大小 若是前者小於後者也就是 1號局部變量 i<100 跳轉到第2行
而後進行往復循環,直到條件不知足,而後順序到return

intFor()
0.  加載常量0 到操做數棧
1.  保存棧頂元素到1號局部變量 j=0;
2.  加載常量0到操做數棧
3.  保存棧頂元素到2號局部變量i=0;
4.  跳轉到13行
13.  加載2號局部變量到操做數棧
14.  加載常量100到操做數棧
16.  比較大小,若是前者 2號局部變量 i <100 跳轉到7
7.  1號局部變量以增量1  自增 j++
10.   2號局部變量以增量1 自增 i++
13.  2號局部變量加載到操做數棧
14.  加載常量100到操做數棧
16.  比較大小,若是前者 2號局部變量 i <100 跳轉到7
往復循環 若是條件不知足 從16 順序到19 結束方法 return
 

public void fun() {
int i = 0;
if(i<2) {
i++;
}else {
i--;
}
}

 

image_5b879225_6ec9_thumb[1]

0, 加載常量0 到棧頂
1,保存棧頂元素 (0) 到1號局部變量
2. 加載1號局部變量到棧頂
3. 加載常量2 到棧頂
4,比較
若是大於後者等於跳轉到13 而後1號局部變量 自增1 而後下一步順序到16 return
不然就是順序執行到7 1號局部變量 增量爲-1  自增運算 而後到10 ,10爲跳轉到16 return

 
 

方法調用相關指令

public void invoker() {
method(2);
}

public void method(int i) {
if(i>5) {
System.out.println(i);
}
}

 

image_5b879225_55f1_thumb[1]

invoker()
0,加載0號 局變量到棧   (上面基本都是第一個數據被保存到1號局部變量,0 號實際上是被this 佔用了)
1,加載常量2 到操做數棧
2.調用實例方法(I)V
5 return

method(int)
0. 加載1號局部變量到操做數棧
1. 加載常量5 到操做數棧
2比較若是小於等於 跳轉到12行 直接返回
若是大於
那麼順序執行到5行       out 是類型爲PrintStream的   System中的靜態變量
8 加載1號局部變量到操做數棧
9 調用實例方法 println  是  PrintStream的實例方法 使用invokevirtual
 

switch 相關

int i = 5;
int j = 6;
switch (i) {
case 1:
j = j + 1;
break;
case 3:
j = j + 2;
break;
case 5:
j = j + 3;
break;
default:
j = j + 4;
}

 

image_5b879225_5641_thumb[1]

0,1,2,4 分別將 5 和 6 加載並存儲到1號和2號局部變量
5.加載1號局部變量到棧  對應 switch (i) {
而後根據tableswitch 表 進行跳轉
雖然咱們只有1,3,5  可是設置了1到5 ,對於2 和 4 直接跳轉到default

40: 2號局部變量 +1
順序到43
43: 跳轉到61 return

46: 2號局部變量 +2
順序到49
49: 跳轉到61 return
 
52: 2號局部變量 +3
順序到55
55: 跳轉到61 return

58 2號局部變量 +4
順序到61 return


int j = 6;
String string = "hehe";
switch (string) {
case "A":
j = j + 1;
break;
case "hehe":
j = j + 2;
break;
case "C":
j = j + 3;
break;
default:
j = j + 4;
}

 

image_5b879226_5e68_thumb[4]

0  加載常量6到棧
1 保存到 1 號局部變量
3.加載常量池 #36 到棧
image_5b879226_6b5c_thumb[1]
5 保存到2 號局部變量
6 加載2號局部變量 到棧
7 複製棧頂元素
8 複製的元素保存到3號局部變量
9 調用String實例化方法hashCode
12, lookupswitch表中,不在相似tableswitch 了,那個是連續的
lookupswitch  是不連續的
咱們總共有三個case一個default
lookupswitch 總共有4項
"A" 的hashCode  爲 65
"C" 的hashCode爲 67
"hehe" 的hashCode爲 3198650  不信的話,本身寫個main方法打印下

通過12行 路由以後跳轉到指定的序列
你會發現三個case他們的過程是同樣的
加載3號局部變量 ,而後將常量 A C  hehe 也加載到棧
而後調用equal方法進行比較
image_5b879226_523c_thumb[1]


代碼千千萬,本文只是找一些基本的示例展現字節碼與代碼的對應關係,想要熟悉這塊
惟有沒事多javap看看你代碼的class文件,才能通宵領悟,進而更好地優化你的代碼

好比看看下面的一個很典型的例子
int i = 5;
int j = 8;
int k = i+j;

int l = 3+6;
image_5b879226_3a97_thumb[1]

前一部分:
0. 常量5 加載到棧
1,保存到 1號局部變量
2. 常量8 加載到棧
4 保存到2號 局部變量
5,加載1號局部變量
6, 加載2號局部變量
7 執行iadd 結果會壓入棧頂
8 棧頂元素保存到3號局部變量
至此 完成了前三行代碼

後一部分:
9.常量9 加載到棧   (3+6  已經被計算好了)
11,保存到4號局部變量
相關文章
相關標籤/搜索