文件結(jié)構(gòu)
Java class文件是8位字節(jié)的二進(jìn)制流萄唇,數(shù)據(jù)項(xiàng)按順序存儲(chǔ)在class文件中,相鄰的項(xiàng)之間沒有任何間隔,這樣可以使class文件緊湊姓赤。占據(jù)多個(gè)字節(jié)空間的項(xiàng)按照高位在前的順序分為幾個(gè)連續(xù)的字節(jié)存放。在class文件中杉辙,可變長度項(xiàng)的大小和長度位于其實(shí)際數(shù)據(jù)之前模捂。這個(gè)特性使得class文件流可以從頭到尾被順序解析,首先讀出項(xiàng)的大小蜘矢,然后讀出項(xiàng)的數(shù)據(jù)狂男。Class文件中有兩種數(shù)據(jù)結(jié)構(gòu):無符號(hào)數(shù)和表∑犯梗可以對(duì)比xml岖食、json,二進(jìn)制文件沒有空格和換行舞吭,節(jié)省空間泡垃,提高性能,但放棄了可讀性羡鸥。
魔數(shù)
每個(gè)Java class文件的前4個(gè)字節(jié)被稱為它的魔數(shù)(magic number):0xCAFEBABE蔑穴。魔數(shù)的作用在于,可以輕松地分辨出Java class文件和非Java class文件惧浴。
class文件的下面4個(gè)字節(jié)包含了主存和、次版本號(hào)。對(duì)于Java虛擬機(jī)來說衷旅,版本號(hào)確定了特定的class文件格式捐腿,通常只有給定主版本號(hào)和一系列次版本號(hào)后,Java虛擬機(jī)才能夠讀取class文件柿顶。如52對(duì)應(yīng)JDK1.8茄袖。
常量池
constant_pool_count和constant_pool
constant_pool_count:兩個(gè)字節(jié)表示常量池的長度,編號(hào)從1開始嘁锯;
CP_info:每個(gè)常量池入口都從一個(gè)長度為一個(gè)字節(jié)的標(biāo)志開始(tag)宪祥,這個(gè)標(biāo)志指出了列表中該位置的常量類型。JDK 1.7以后共有14種不相同表結(jié)構(gòu)的數(shù)據(jù)家乘。
訪問標(biāo)志access_flags
緊接常量池后的兩個(gè)字節(jié)稱為access_flags品山,它展示了文件中定義的類或接口的幾段信息,包括這個(gè)Class是類還是接口烤低;是否定義為public類型肘交;是否為abstrct類型;在access_flags中所有未使用的位都必須由編譯器置0扑馁,而且Java虛擬機(jī)必須忽略它涯呻。
類索引
接下來的兩個(gè)字節(jié)為this_class項(xiàng)凉驻,它是一個(gè)對(duì)常量池的索引。在this_class位置的常量池入口必須為CONSTANT_Class_info表复罐。該表由兩個(gè)部分組成——標(biāo)簽和name_index涝登。標(biāo)簽部分是一個(gè)具有CONSTANT_Class值的常量,在name_index位置的常量池入口為一個(gè)包含了類或接口全限定名的CONSTANT_Utf8_info表效诅。
父類索引與接口索引集合同理胀滚。
字段表集合
緊接在interfaces后面的是對(duì)在該類或者接口中所聲明的字段的描述。首先是名為fields_count的計(jì)數(shù)乱投,它是類變量和實(shí)例變量的字段的數(shù)量總和咽笼。在這個(gè)計(jì)數(shù)后面的是不同長度的field_info表的序列(fields_count指出了序列中有多少個(gè)field_info表)。在fields列表中戚炫,不列出從超類或者父接口繼承而來的字段剑刑。字段表結(jié)構(gòu)如下圖所示:
方法表集合
緊接著fields后面的是對(duì)在該類或者接口中所聲明的方法的描述。只包括在該類或者接口中顯式定義的方法双肤。
屬性表集合
在Class文件施掏、字段表、方法表中都可以攜帶自己的屬性表集合茅糜。相對(duì)于其它表七芭,屬性表的限制相對(duì)較小,不再要求各個(gè)屬性表有嚴(yán)格的順序蔑赘,可以寫入自定義的屬性信息狸驳,JVM也預(yù)定義了21項(xiàng)屬性表。對(duì)于每個(gè)屬性米死,它的名稱需要從常量池中引入一個(gè)CONSTANT_Utf8_info類型的常量來表示,而屬性值的結(jié)構(gòu)則完全自定義贮庞,只需要一個(gè)u4的長度屬性去說明屬性值所占用的位數(shù)即可峦筒,一個(gè)屬性表結(jié)構(gòu)如下圖所示:
字節(jié)碼指令
Java虛擬機(jī)的指令由一個(gè)字節(jié)長度的、代表著某種特定操作含義的數(shù)字(稱為操作碼窗慎,Opcode)以及跟隨其后的零至多個(gè)代表此操作所需參數(shù)(稱為操作數(shù)物喷,Operands)而構(gòu)成。操作碼的長度為1個(gè)字節(jié)遮斥,因此最大只有256條峦失,是基于棧的指令集架構(gòu)。
字節(jié)碼與數(shù)據(jù)類型
在Java虛擬機(jī)的指令集中术吗,大多數(shù)的指令都包含了其操作所對(duì)應(yīng)的數(shù)據(jù)類型信息尉辑。iload中的i表示的是int。i代表對(duì)int類型的數(shù)據(jù)操作较屿,l代表long隧魄,s代表short卓练,b代表byte,c代表char购啄,f代表float襟企,d代表double,a代表reference狮含。也有不包含類型信息的:goto與類型無關(guān)顽悼;Arraylength操作數(shù)組類型。
加載與存儲(chǔ)指令
加載和存儲(chǔ)指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來回傳輸几迄,這類指令包括:
- 將一個(gè)局部變量加載到操作棧:iload
- 將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:istore
- 將一個(gè)常量加載到操作數(shù)棧:bipush蔚龙。
- 擴(kuò)充局部變量表的訪問索引的指令:wide
enter description here
運(yùn)算指令
運(yùn)算或算術(shù)指令用于對(duì)兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定運(yùn)算,并把結(jié)果重新存入到操作棧頂乓旗。大體上算術(shù)指令可以分為兩種:對(duì)整型數(shù)據(jù)進(jìn)行運(yùn)算的指令與對(duì)浮點(diǎn)型數(shù)據(jù)進(jìn)行運(yùn)算的指令府蛇,無論是哪種算術(shù)指令,都使用Java虛擬機(jī)的數(shù)據(jù)類型屿愚,由于沒有直接支持byte汇跨、short、char和boolean類型的算術(shù)指令妆距,對(duì)于這類數(shù)據(jù)的運(yùn)算穷遂,應(yīng)使用操作int類型的指令代替。
注:e = a + b + c + d +e娱据,操作數(shù)棧的深度依然是2蚪黑。
類型轉(zhuǎn)換指令
類型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類型進(jìn)行相互轉(zhuǎn)換,這些轉(zhuǎn)換操作一般用于實(shí)現(xiàn)用戶代碼中的顯示類型轉(zhuǎn)換操作中剩,或者用來處理字節(jié)碼指令中數(shù)據(jù)類型相關(guān)指令無法與數(shù)據(jù)類型一一對(duì)應(yīng)的問題忌穿。
寬化類型處理:int i= 1;long l = i结啼;
窄化類型處理:User user = new User(); Object obj = user;
處理窄化類型轉(zhuǎn)換時(shí)掠剑,必須顯式地使用轉(zhuǎn)換指令來完成,這些轉(zhuǎn)換指令包括:i2b郊愧、i2c朴译、i2s、l2i属铁、f2i眠寿、f2l、d2i焦蘑、d2l和d2f盯拱。
對(duì)象創(chuàng)建與訪問指令
雖然類實(shí)例和數(shù)組都是對(duì)象,但Java虛擬機(jī)對(duì)類實(shí)例和數(shù)組的創(chuàng)建與操作使用了不同的字節(jié)碼指令。對(duì)象創(chuàng)建后坟乾,就可以通過對(duì)象訪問指令獲取對(duì)象實(shí)例或者數(shù)組實(shí)例中的字段或者數(shù)組元素迹辐,這些指令如下:
- 創(chuàng)建類實(shí)例的指令:new;
- 創(chuàng)建數(shù)組的指令:newarray、anewarray、multianewarray凌唬;
- 訪問類字段(static字段蒲肋,或者稱為類變量)和實(shí)例字段(非static字段,或者稱為實(shí)例變量)的指令:getstatic、putstatic、getfield、putfield仍律;
- 把一個(gè)數(shù)組元素加載到操作數(shù)棧的指令:baload、caload实柠、saload水泉、iaload、laload窒盐、faload草则、daload、aaload蟹漓;
- 將一個(gè)操作數(shù)棧的值存儲(chǔ)到數(shù)組元素中的指令:bastore炕横、castore、sastore葡粒、iastore份殿、fastore、dastore嗽交、aastore卿嘲;
- 取數(shù)組長度的指令:arraylength;
- 檢查類實(shí)例類型的指令:instanceof夫壁、checkcast拾枣。
class Demo
{
public static void main(String[ ] args){
User user = new User();
User[] users = new User[10];
int[] is = new int[10];
user.name = "hello";
String username = user.name;
}
}
class User{
String name;
static int age;
}
字節(jié)碼指令為:
Code:
stack=2, locals=5, args_size=1
0: new #2 // class User
3: dup
4: invokespecial #3 // Method User."<init>":()V
7: astore_1
8: bipush 10
10: anewarray #2 // class User
13: astore_2
14: bipush 10
16: newarray int
18: astore_3
19: aload_1
20: ldc #4 // String hello
22: putfield #5 // Field User.name:Ljava/lang/String;
25: aload_1
26: getfield #5 // Field User.name:Ljava/lang/String;
29: astore 4
31: return
1234567891011121314151617181920
操作數(shù)棧管理指令
如同操作一個(gè)普通數(shù)據(jù)結(jié)構(gòu)中的堆棧那樣,Java虛擬機(jī)提供了一些用于直接操作操作數(shù)棧的指令掌唾,包括:
- 將操作數(shù)棧的棧頂一個(gè)或兩個(gè)元素出棧:pop放前、pop2忿磅;(不常用)
- 復(fù)制棧頂一個(gè)或兩個(gè)數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup糯彬、dup2、dup_x1葱她、dup2_x1撩扒、dup_x2、dup2_x2;
- 將棧最頂端的兩個(gè)數(shù)值互換:swap搓谆。
控制轉(zhuǎn)移指令
控制轉(zhuǎn)移指令可以讓Java虛擬機(jī)有條件或無條件地從指定的位置指令而不是控制轉(zhuǎn)移指令的下一條指令繼續(xù)執(zhí)行程序炒辉,從概念模型上理解,可以認(rèn)為控制轉(zhuǎn)移指令就是在有條件或無條件地修改PC寄存器的值泉手。如:goto等黔寇。
class Demo {
public static void main(String[ ] args){
int a = 10;
if(a > 10){
System.out.println(">10");
}else{
System.out.println(">=10");
}
}
}
字節(jié)碼指令為:
Code:
stack=2, locals=2, args_size=1
0: bipush 10
2: istore_1
3: iload_1
4: bipush 10
6: if_icmple 20
9: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
12: ldc #3 // String >10
14: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: goto 28
20: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
23: ldc #5 // String >=10
25: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
方法調(diào)用
- invokevirtual:用于調(diào)用對(duì)象的實(shí)例方法,根據(jù)對(duì)象的實(shí)際類型進(jìn)行分派(虛方法分派)斩萌,這也是Java語言中最常見的方法分派方式缝裤。
- invokeinterface:用于調(diào)用接口方法,它會(huì)在運(yùn)行時(shí)搜索一個(gè)實(shí)現(xiàn)了這個(gè)接口方法的對(duì)象颊郎,找出適合的方法進(jìn)行調(diào)用憋飞。
- invokespecial:用于調(diào)用一些需要特殊處理的實(shí)例方法,包括實(shí)例初始化方法姆吭、私有方法和父類方法榛做。
- invokestatic:用于調(diào)用類方法(static 方法)
異常處理指令
在Java程序中顯示拋出異常的操作(throw語句)都由athrow指令來實(shí)現(xiàn),除了用throw語句顯示拋出異常情況之外内狸,Java虛擬機(jī)規(guī)范還規(guī)定了許多運(yùn)行時(shí)異常會(huì)在其他Java虛擬機(jī)指令檢測(cè)到異常狀況時(shí)自動(dòng)拋出检眯。而在Java虛擬機(jī)中,處理異常(catch語句)不是由字節(jié)碼指令來實(shí)現(xiàn)的答倡,而是采用異常表來完成的轰传。
class Demo
{
public static void main(String[ ] args){
int a = 0;
throw new RuntimeException("Exception......");
}
}
12345678
字節(jié)碼指令為:
Code:
stack=3, locals=2, args_size=1
0: iconst_0
1: istore_1
2: new #2 // class java/lang/RuntimeException
5: dup
6: ldc #3 // String Exception......
8: invokespecial #4 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
11: athrow
12345678910