字節(jié)碼剖析
示例代碼:
package com.leofight.jvm.bytecode;
public class MyTest1 {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
反編譯信息如下:
classes javap -verbose com.leofight.jvm.bytecode.MyTest1
Classfile /Users/lz/IdeaProjects/jvm_lecture/out/production/classes/com/leofight/jvm/bytecode/MyTest1.class
Last modified Jul 29, 2018; size 497 bytes
MD5 checksum 962ee95ed59fdba7d6d90375419bc0ff
Compiled from "MyTest1.java"
public class com.leofight.jvm.bytecode.MyTest1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // com/leofight/jvm/bytecode/MyTest1.a:I
#3 = Class #22 // com/leofight/jvm/bytecode/MyTest1
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/leofight/jvm/bytecode/MyTest1;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 MyTest1.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/leofight/jvm/bytecode/MyTest1
#23 = Utf8 java/lang/Object
{
public com.leofight.jvm.bytecode.MyTest1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 3: 0
line 5: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/leofight/jvm/bytecode/MyTest1;
public int getA();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/leofight/jvm/bytecode/MyTest1;
public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 13: 0
line 14: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/leofight/jvm/bytecode/MyTest1;
0 6 1 a I
}
SourceFile: "MyTest1.java"
字節(jié)碼文件16進(jìn)制
CAFEBABE 00000034 00180A00 04001409 00030015 07001607 00170100 01610100 01490100 063C696E 69743E01 00032829 56010004 436F6465 01000F4C 696E654E 756D6265 72546162 6C650100 124C6F63 616C5661 72696162 6C655461 626C6501 00047468 69730100 234C636F 6D2F6C65 6F666967 68742F6A 766D2F62 79746563 6F64652F 4D795465 7374313B 01000467 65744101 00032829 49010004 73657441 01000428 49295601 000A536F 75726365 46696C65 01000C4D 79546573 74312E6A 6176610C 00070008 0C000500 06010021 636F6D2F 6C656F66 69676874 2F6A766D 2F627974 65636F64 652F4D79 54657374 31010010 6A617661 2F6C616E 672F4F62 6A656374 00210003 00040000 00010002 00050006 00000003 00010007 00080001 00090000 00380002 00010000 000A2AB7 00012A04 B50002B1 00000002 000A0000 000A0002 00000003 00040005 000B0000 000C0001 0000000A 000C000D 00000001 000E000F 00010009 0000002F 00010001 00000005 2AB40002 AC000000 02000A00 00000600 01000000 09000B00 00000C00 01000000 05000C00 0D000000 01001000 11000100 09000000 3E000200 02000000 062A1BB5 0002B100 00000200 0A000000 0A000200 00000D00 05000E00 0B000000 16000200 00000600 0C000D00 00000000 06000500 06000100 01001200 00000200 13
Java字節(jié)碼結(jié)構(gòu)
Class字節(jié)碼中有兩種數(shù)據(jù)類型:
- 字節(jié)數(shù)據(jù)直接量:這是基本的數(shù)據(jù)類型材部。共細(xì)分為u1、u2、u4悼吱、u8四種,分別代表連續(xù)的1個(gè)字節(jié)良狈、2個(gè)字節(jié)后添、4個(gè)字節(jié)、8個(gè)字節(jié)組成的整體數(shù)據(jù)薪丁。
- 表(數(shù)組):表是由多個(gè)基本數(shù)據(jù)或其他表遇西,既按照既定順序組成的大的數(shù)據(jù)結(jié)合。表是有結(jié)構(gòu)的严嗜,它的結(jié)構(gòu)體現(xiàn)在:組成表的成分所在的位置和順序都是嚴(yán)格定義好的粱檀。
使用javap -verbose
命令分析一個(gè)字節(jié)碼文件時(shí),將會分析該字節(jié)碼文件的魔數(shù)漫玄、版本號茄蚯、常量池、類信息睦优、類的構(gòu)造方法渗常、類中的方法信息、類變量與成員變量等信息汗盘。
魔數(shù):所有的.class字節(jié)碼文件的前4個(gè)字節(jié)都是魔數(shù)皱碘,魔數(shù)值為固定值:0xCAFEBABE。
版本號::魔數(shù)之后的4個(gè)字節(jié)為版本信息衡未,前兩個(gè)字節(jié)表示minor version
(次版本號)尸执,后兩個(gè)字節(jié)表示major version
(主版本號),這里的版本號為 00 00 00 34缓醋,換算成十進(jìn)制,表示次版本號為0如失,主版本號為52。所以該文件的版本號為:1.8.0送粱⊥使螅可以通過java -version
來驗(yàn)證這一點(diǎn)。
? classes java -version
java version "1.8.0_66"
Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)
常量池(constant pool):緊接著主版本號之后 就是常量池的入口。一個(gè)Java類中定義的很多信息都是由常量池來維護(hù)和描述的脆丁,可以將常量池看作是Class文件的資源倉庫世舰,比如說Java類中定義的方法與變量信息,都是存儲在常量池中槽卫。常量池中主要存儲兩類常量:字面量與符號引用跟压。字面量如文本字符串,Java中聲明為final的常量值等歼培,而符號引用如類和接口的全局限定名震蒋,字段的名稱和描述符,方法的名稱和描述符等躲庄。
常量池的總體結(jié)構(gòu): Java類所對應(yīng)的常量池主要由常量池?cái)?shù)量與常量池?cái)?shù)組(常量表)這兩部分公共組成查剖。常量池?cái)?shù)量僅跟在主版本號后面,占據(jù)2個(gè)字節(jié)噪窘;常量池?cái)?shù)組僅跟在常量池?cái)?shù)量之后笋庄。常量池?cái)?shù)組與一般的數(shù)組不同的是,常量池?cái)?shù)組中不同的元素的類型倔监、結(jié)構(gòu)都是不同的直砂,長度當(dāng)然也就不同;但是浩习,每一種元素的第一個(gè)數(shù)據(jù)都是u1類型哆键,該字節(jié)是個(gè)標(biāo)志位,占據(jù)1個(gè)字節(jié)瘦锹。JVM在解析常量池時(shí),會根據(jù)這個(gè)u1類型來獲取元素的具體類型闪盔。值的注意的是弯院,常量池?cái)?shù)組中元素的個(gè)數(shù) = 常量池?cái)?shù) - 1 (其中0暫時(shí)不使用),目的是滿足某些常量池索引值的數(shù)據(jù)在特定情況下需要表達(dá)【不引用任何一個(gè)常量池】的含義泪掀;根本原因在于听绳,索引為0也是一個(gè)常量(保留常量),只不過它位于常量表中异赫,這個(gè)常量就對應(yīng)null值椅挣;所以,常量池的索引從1而非0開始塔拳。
在上面的表中描述了11種數(shù)據(jù)類型的結(jié)構(gòu)鼠证,其實(shí)在jdk1.7之后又增加了3種(CONSTANT_MethodHandle_info,CONSTANT_MethodType_info以及CONSTANT_InvokeDynamic_info)。這樣一共是14種靠抑。
在JVM規(guī)范中量九,每個(gè)變量/字段都有描述信息,描述信息主要的作用是描述字段的數(shù)據(jù)類型、方法的參數(shù)類型(包括數(shù)量荠列、類型與順序)與返回值类浪。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型和代表無返回值的void類型都是用一個(gè)大寫字符來表示肌似,對象類型則使用字符L加對象的全限定名稱來表示费就。為了壓縮字節(jié)碼文件的體積,對于基本數(shù)據(jù)類型川队,JVM都只使用一個(gè)大寫字母來表示力细,如下所示:B - byte, C - char呼寸, D - double艳汽,F(xiàn) - float, I - int 对雪,J - long河狐,S - short, Z - boolean瑟捣, V - void馋艺,L - 對象類型,如Ljava/lang/String;
對于數(shù)組類型來說迈套,每一個(gè)維度使用一個(gè)前置的[來表示捐祠,如int[]被記錄為[I,String[][]被記錄為[[Ljava/lang/String;
用描述符描述方法時(shí),按照先參數(shù)列表桑李,后返回值的順序來描述踱蛀。參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組()之內(nèi),如方法:String getRealnamebyIdAndNickname(int id贵白,String name)
的描述符為:(I,Ljava/lang/String;)Ljava/lang/String;
Access_Flag 訪問標(biāo)志:訪問標(biāo)志信息包括該Class文件是類還是接口率拒,是否被定義成public,是否是abstract禁荒,如果是類猬膨,是否被聲明成final。通過上面的源代碼呛伴,我們知道該文件是類并且是public勃痴。
0x 00 21:是0×0020和0×0001的并集,表示ACC_PUBLIC與ACC_SUPER 热康。
類索引:用于確定類的全限定名沛申,0×00 03 表示引用第3個(gè)常量,同時(shí)第3個(gè)常量引用第22個(gè)常量姐军,查找得#22 = Utf8 com/leofight/jvm/bytecode/MyTest1
父類索引:0×00 04 同理:#4.#23(java/lang/Object)
接口索引:接口有2+n個(gè)字節(jié)污它,前兩個(gè)字節(jié)表示的是接口數(shù)量,后面跟著就是接口的表。我們這個(gè)類沒有任何接口衫贬,所以應(yīng)該是0000德澈。
字段表集合:字段表用于描述類和接口中聲明的變量。這里的字段包含了類級別變量以及實(shí)例變量固惯,但是不包括方法內(nèi)部聲明的局部變量梆造。
0×00 02 :訪問標(biāo)志為private
0×00 05 : 字段名稱索引為#5镇辉,對應(yīng)的是”a”
0x 00 06 :描述符索引為#6烂斋,對應(yīng)的是”I”
0x 00 00 :屬性表數(shù)量為0罕模,因此沒有屬性表蝶念。
方法表:
方法中的每個(gè)屬性都是一個(gè)attribute_info結(jié)構(gòu)
JVM預(yù)定義了部分attribute适袜,但是編譯器自己也可以實(shí)現(xiàn)自己的attribute寫入class文件里,共運(yùn)行時(shí)使用疫萤。不同的attribute通過attribute_name_index來區(qū)分。
Code結(jié)構(gòu):Code attribute的作用是保存該方法的結(jié)構(gòu)钓丰,如所對應(yīng)的字節(jié)碼
attribute_length表示attribute所包含的字節(jié)數(shù),不包含attribute_name_index和attribute_length字段。
max_stack表示這個(gè)方法運(yùn)行的任何時(shí)刻所能達(dá)到的操作數(shù)棧的最大深度存筏。
max_locals表示方法執(zhí)行期間創(chuàng)建的局部變量的數(shù)目方篮,包含用來表示傳入的參數(shù)局部變量励负。
code_length表示該方法所包含的字節(jié)碼的字節(jié)數(shù)以及具體的指令碼
具體字節(jié)碼即是該方法被調(diào)用時(shí),虛擬機(jī)所執(zhí)行的字節(jié)碼
exception_table,這里存放的是處理異常的信息
每個(gè)exception_table表項(xiàng)由start_pc巾表,end_pc,handler_pc,catch_type組成。
start_pc和end_pc表示在code數(shù)組中的從start_pc到end_pc處(包含start_pc集币,不包含end_pc)的指令拋出的異常會由這個(gè)表項(xiàng)來處理
handler_pc表示處理異常的代碼的開始處鞠苟。catch_type表示會被處理的異常類型考榨,它指向常量池里的一個(gè)異常類冀惭。當(dāng)catch_type為0時(shí)震叙,表示處理所有的異常。
附加屬性
LineNumberTable:這個(gè)屬性用來表示code數(shù)組中的字節(jié)碼和Java代碼行數(shù)之間的關(guān)系散休。這個(gè)屬性可以用來在調(diào)試的時(shí)候定位代碼執(zhí)行的行數(shù)溃槐。