Java字節(jié)碼詳解(二)字節(jié)碼的運(yùn)行過(guò)程
2018年10月23日 17:31:04 talex 閱讀數(shù) 677
<article class="baidu_pl" style="box-sizing: inherit; outline: 0px; margin: 0px; padding: 16px 0px 0px; display: block; position: relative; color: rgba(0, 0, 0, 0.75); font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: common-ligatures; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
文章目錄
前一章講述了java字節(jié)碼文件的生成以及字節(jié)碼文件中各個(gè)字段代表的含義惩猫,在本章節(jié)將講述字節(jié)碼是什么運(yùn)行的
JVM的一些基礎(chǔ)概念
要理解java字節(jié)碼的運(yùn)行情況羔巢,首先要了解有關(guān)JVM的一些知識(shí)交惯,這些是java字節(jié)碼運(yùn)行的先決條件墅诡。
JVM數(shù)據(jù)類(lèi)型
Java是靜態(tài)類(lèi)型的,它會(huì)影響字節(jié)碼指令的設(shè)計(jì)障癌,這樣指令就會(huì)期望自己對(duì)特定類(lèi)型的值進(jìn)行操作凌外。例如,就會(huì)有好幾個(gè)add指令用于兩個(gè)數(shù)字相加:iadd涛浙、ladd康辑、fadd、dadd轿亮。他們期望類(lèi)型的操作數(shù)分別是int疮薇、long、float和double哀托。大多數(shù)字節(jié)碼都有這樣的特性惦辛,它具有不同形式的相同功能劳秋,這取決于操作數(shù)類(lèi)型仓手。
JVM定義的數(shù)據(jù)類(lèi)型包括:
- 基本類(lèi)型:
- 數(shù)值類(lèi)型: byte (8位), short (16位), int (32位), long (64-bit位), char (16位無(wú)符號(hào)Unicode), float(32-bit IEEE 754 單精度浮點(diǎn)型), double (64-bit IEEE 754 雙精度浮點(diǎn)型)
- 布爾類(lèi)型
- 指針類(lèi)型: 指令指針。
- 引用類(lèi)型:
- 類(lèi)
- 數(shù)組
- 接口
在字節(jié)碼中布爾類(lèi)型的支持是受限的玻淑。舉例來(lái)說(shuō)嗽冒,沒(méi)有結(jié)構(gòu)能直接操作布爾值。布爾值被替換轉(zhuǎn)換成 int 是通過(guò)編譯器來(lái)進(jìn)行的补履,并且最終還是被轉(zhuǎn)換成 int 結(jié)構(gòu)添坊。Java 開(kāi)發(fā)者應(yīng)該熟悉所有上面的類(lèi)型,除了 returnAddress箫锤,它沒(méi)有等價(jià)的編程語(yǔ)言類(lèi)型贬蛙。類(lèi)數(shù)組接口在字節(jié)碼中布爾類(lèi)型的支持是受限的。舉例來(lái)說(shuō)谚攒,沒(méi)有結(jié)構(gòu)能直接操作布爾值阳准。布爾值被替換轉(zhuǎn)換成 int 是通過(guò)編譯器來(lái)進(jìn)行的,并且最終還是被轉(zhuǎn)換成 int 結(jié)構(gòu)馏臭。
Java 開(kāi)發(fā)者應(yīng)該熟悉所有上面的類(lèi)型野蝇,除了 returnAddress,它沒(méi)有等價(jià)的編程語(yǔ)言類(lèi)型。
JVM的內(nèi)存結(jié)構(gòu)
JVM的內(nèi)存分布如上圖所示绕沈。方法區(qū)和堆是線程共享的锐想,而寄存器、java方法棧乍狐、本地方法棧是各個(gè)線程私有的赠摇。
1.方法區(qū)
方法區(qū)是用來(lái)存儲(chǔ)已被JVM加載的類(lèi)信息、常量浅蚪、靜態(tài)變量蝉稳、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
這個(gè)區(qū)域很少進(jìn)行垃圾回收掘鄙,回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類(lèi)型的卸載耘戚。
2.堆
此區(qū)域唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存.
3.PC寄存器
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間操漠,線程私有收津。它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器
4. Java方法棧和本地方法棧
JVM棧描述的是java方法執(zhí)行的內(nèi)存模型,每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀浊伙,用于存儲(chǔ)局部變量表撞秋、操作數(shù)棧、動(dòng)態(tài)鏈接嚣鄙、方法出口等信息.
Java字節(jié)碼的運(yùn)行就是在JVM方法棧中進(jìn)行的
Java字節(jié)碼運(yùn)行過(guò)程
簡(jiǎn)單的示例
1.示例源碼
先來(lái)看我們的例子代碼吻贿,源碼如下:
public class Test{
public static void main(String[] args){
Integer a = 1;
Integer b = 2;
Integer c = a + b;
}
}
2.main函數(shù)的字節(jié)碼展示
使用javac Test.java
進(jìn)行編譯,然后使用javap -v Test.class
查看該java文件的字節(jié)碼哑子,為了排除干擾舅列,去除了很多不必要的字節(jié)碼
*** 省略部分字節(jié)碼
Constant pool:
#1 = Methodref #5.#14 // java/lang/Object."<init>":()V
#2 = Methodref #15.#16 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#3 = Methodref #15.#17 // java/lang/Integer.intValue:()I
*** 省略部分字節(jié)碼
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: iconst_2
6: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
9: astore_2
10: aload_1
11: invokevirtual #3 // Method java/lang/Integer.intValue:()I
14: aload_2
15: invokevirtual #3 // Method java/lang/Integer.intValue:()I
18: iadd
19: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
22: astore_3
23: return
3.字節(jié)碼指令運(yùn)行過(guò)程
接下來(lái)分析Code
中字節(jié)碼運(yùn)行的過(guò)程。這里說(shuō)一下卧蜓,每個(gè)指令前的數(shù)字為指令在寄存器中的偏移量帐要。
0: iconst_1
將int常量1進(jìn)行放入操作數(shù)棧。這里稍微做個(gè)拓展弥奸,如果將float常量2進(jìn)行入棧操作榨惠,name該指令是fconst_2
,詳細(xì)的指令種類(lèi)及意義請(qǐng)查看下一章 Java字節(jié)碼指令詳解盛霎。
1: invokestatic #2
調(diào)用常量池中序號(hào)為#2
的靜態(tài)方法赠橙,這里調(diào)用的是 Integer.valueOf()方法,表示將該int類(lèi)型進(jìn)行裝箱操作愤炸,變?yōu)镮nteger類(lèi)型
4: astore_1
在索引為1的位置將第一個(gè)操作數(shù)出棧(一個(gè)Integer值)并且將其存進(jìn)本地變量期揪,相當(dāng)于變量a。
5: iconst_2
將int常量2進(jìn)行放入操作數(shù)棧
6: invokestatic #2
調(diào)用常量池中序號(hào)為#2
的靜態(tài)方法摇幻,這里調(diào)用的是 Integer.valueOf()方法横侦,表示將該int類(lèi)型進(jìn)行裝箱操作挥萌,變?yōu)镮nteger類(lèi)型
9: astore_2
在索引為2的位置將第一個(gè)操作數(shù)出棧(一個(gè)Integer值)并且將其存進(jìn)本地變量,相當(dāng)于變量b枉侧。
10: aload_1
從索引1的本地變量中加載一個(gè)int值引瀑,放入操作數(shù)棧
11: invokevirtual #3
調(diào)用常量池中序號(hào)為#3
的實(shí)例方法,這里調(diào)用的是 Integer.intValue()方法
14: aload_2
從索引1的本地變量中加載一個(gè)int值榨馁,放入操作數(shù)棧
15: invokevirtual #3
調(diào)用常量池中序號(hào)為#3
的實(shí)例方法憨栽,這里調(diào)用的是 Integer.intValue()方法
18: iadd
把操作數(shù)棧中的前兩個(gè)int值出棧并相加,將相加的結(jié)果放入操作數(shù)棧翼虫。
19: invokestatic #2
調(diào)用常量池中序號(hào)為#2
的靜態(tài)方法屑柔,這里調(diào)用的是 Integer.valueOf()方法
22: astore_3
在索引為3的位置將第一個(gè)操作數(shù)出棧(一個(gè)Integer值)并且將其存進(jìn)本地變量,相當(dāng)于變量c珍剑。
23: return
方法結(jié)束
方法調(diào)用
上面的示例是比較簡(jiǎn)單的掸宛,而且只有一個(gè)main函數(shù),接下來(lái)將展示在多個(gè)函數(shù)時(shí)候字節(jié)碼的形式以及運(yùn)行的具體過(guò)程招拙。這里就直接拿參考文章的示例唧瘾,原文寫(xiě)得真的很好,有條件可以去看英文原文。 字節(jié)碼的介紹
1.示例源碼
public class Test{
public static void main(String[] args){
int a = 1;
int b = 2;
int c = calc(1,2);
}
static int calc(int a,int b){
return (int) Math.sqrt(Math.pow(a,2)+Math.pow(b,2));
}
}
2.字節(jié)碼展示
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iconst_1
5: iconst_2
6: invokestatic #2 // Method calc:(II)I
9: istore_3
10: return
static int calc(int, int);
descriptor: (II)I
flags: ACC_STATIC
Code:
stack=6, locals=2, args_size=2
0: iload_0
1: i2d
2: ldc2_w #3 // double 2.0d
5: invokestatic #5 // Method java/lang/Math.pow:(DD)D
8: iload_1
9: i2d
10: ldc2_w #3 // double 2.0d
13: invokestatic #5 // Method java/lang/Math.pow:(DD)D
16: dadd
17: invokestatic #6 // Method java/lang/Math.sqrt:(D)D
20: d2i
21: ireturn
3. 指令執(zhí)行過(guò)程詳解
上面就是main方法和calc方法的字節(jié)碼别凤,由于main方法的指令跟上個(gè)例子很相似饰序,唯一不同的是 c=a+b
變?yōu)橛蒫alc方法去執(zhí)行并且返回。這里就不再贅述main方法规哪,接下來(lái)主要講解calc方法的執(zhí)行過(guò)程求豫。
0: iload_0
將方法中第一個(gè)參數(shù)入棧
1: i2d
將int類(lèi)型轉(zhuǎn)為double類(lèi)型
2: ldc2_w #3
將常量池序號(hào)為#3
的long型常量從常量池推送至棧頂(寬索引)
5: invokestatic #5
調(diào)用靜態(tài)方法:Math.pow:() ,并且將結(jié)果放入棧頂
8: iload_1
9: i2d
10: ldc2_w #3
13: invokestatic #5
以上的指令跟上一個(gè)一樣诉稍,進(jìn)行平方運(yùn)算
16: dadd
將result和result2相加蝠嘉,并推入棧頂
17: invokestatic #6
調(diào)用Math.sqrt()方法
20: d2i
將double類(lèi)型轉(zhuǎn)為int類(lèi)型
21: ireturn
返回int類(lèi)型的數(shù)值
實(shí)例調(diào)用
修改上面的代碼,加入對(duì)象均唉,并調(diào)用對(duì)象的方法是晨。
public class Test {
public static void main(String[] args){
Point a =new Point (1,2);
Point b = new Point (3,4);
int c = a.area(b);
}
static class Point{
private int x;
private int y;
public Point(int x,int y){
this.x = x;
this.y = y;
}
public int area(Point p){
int length = Math.abs(p.y-this.y);
int width = Math.abs(p.x-this.x);
return length*width;
}
}
}
使用javap -v Test
查看編譯后的字節(jié)碼:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=4, args_size=1
0: new #2 // class Test3$Point
3: dup
4: iconst_1
5: iconst_2
6: invokespecial #3 // Method Test3$Point."<init>":(II)V
9: astore_1
10: new #2 // class Test3$Point
13: dup
14: iconst_3
15: iconst_4
16: invokespecial #3 // Method Test3$Point."<init>":(II)V
19: astore_2
20: aload_1
21: aload_2
22: invokevirtual #4 // Method Test3$Point.area:(LTest3$Point;)I
25: istore_3
26: return
這個(gè)main方法比上一個(gè)例子多了幾個(gè)新的指令:new
,dup
,invokespecial
new
new 指令與編程語(yǔ)言中的 new 運(yùn)算符類(lèi)似,它根據(jù)傳入的操作數(shù)所指定類(lèi)型來(lái)創(chuàng)建對(duì)象(這是對(duì) Point 類(lèi)的符號(hào)引用)舔箭。-
dup
dup指令會(huì)復(fù)制頂部操作數(shù)的棧值,這意味著現(xiàn)在我們?cè)跅m敳坑袃蓚€(gè)指向Point對(duì)象的引用蚊逢。
-
iconst_1
层扶,iconst_2
,invokespecial
烙荷,將x,y的值(1,2)壓入棧頂镜会,接下來(lái)進(jìn)行Point初始化工作,將x,y的值進(jìn)行賦值终抽。初始化完成后會(huì)將棧頂?shù)娜齻€(gè)操作引用銷(xiāo)毀戳表,只留下最初的Point的對(duì)象引用桶至。
-
astore_1
將該P(yáng)oint引用出棧,并將其賦值到索引1所保存的本地變量(astore_1中的a表明這是一個(gè)引用值)
接下來(lái)進(jìn)行第二個(gè)Point實(shí)例的初始化和賦值操作
-
20: aload_1
,21: aload_2
將a,b的Point實(shí)例的引用入棧 -
22: invokevirtual #4
調(diào)用area
方法匾旭, -
25: istore_3
將返回值放入索引3中(即賦值給c) -
return
方法結(jié)束
總結(jié)
本章節(jié)寫(xiě)了字節(jié)碼運(yùn)行的詳細(xì)過(guò)程镣屹,詳細(xì)的指令介紹在下一章,有興趣可以看看价涝。
</article>