[四] 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號局部變量


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM