引言
??我們知道苛白,使用Java編寫的類文件位谋,在經(jīng)過編譯之后多柑,變成.class文件论衍。Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進制流瑞佩,各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符坯台,這使得整個Class文件中存儲的內(nèi)容幾乎全部是程序運行的必要數(shù)據(jù)炬丸。
??根據(jù)Java虛擬機規(guī)范的規(guī)定,Class文件格式采用一種類似于C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲數(shù)據(jù)蜒蕾,這種偽結(jié)構(gòu)只有兩種數(shù)據(jù)類型:無符號數(shù)和表稠炬,下面的解析都要以這兩種數(shù)據(jù)類型為基礎(chǔ),這里先介紹一下咪啡。
??無符號數(shù)屬于基本的數(shù)據(jù)類型首启,以u1、u2瑟匆、u4闽坡、u8來分表代表1個字節(jié)、2個字節(jié)愁溜、4個字節(jié)疾嗅、8個字節(jié)的無符號數(shù),無符號數(shù)可以用來描述數(shù)字冕象、索引引用代承、數(shù)量之或者按照UTF-8編碼構(gòu)成的字符串值等。
??表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構(gòu)成的復(fù)合數(shù)據(jù)類型渐扮,所有表都習(xí)慣性地以“_info”結(jié)尾论悴。表用于描述有層次關(guān)系的復(fù)合解雇的數(shù)據(jù),整個Class文件本質(zhì)上就是一張表墓律,如下圖所示:
分析前準(zhǔn)備
??在分析Class文件結(jié)構(gòu)之前膀估,我們先來生成一個寫一個在Java中最常見的實例類,代碼如下:
public class Person {
private String name;
public int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
如上圖所以耻讽,一個最常見的Person類察纯,有兩個屬性:name和age蜂筹。又生成了各自的set和get方法甜攀。下面使用javac將該Person.java類編譯成Person.class匙监。
javac Person.java
使用文本打開Person.class文件荧嵌,如下所示:
cafe babe 0000 0034 001d 0a00 0500 1809
0004 0019 0900 0400 1a07 001b 0700 1c01
0004 6e61 6d65 0100 124c 6a61 7661 2f6c
616e 672f 5374 7269 6e67 3b01 0003 6167
6501 0001 4901 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0007
6765 744e 616d 6501 0014 2829 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 0100
0773 6574 4e61 6d65 0100 1528 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 2956
0100 0667 6574 4167 6501 0003 2829 4901
0006 7365 7441 6765 0100 0428 4929 5601
000a 536f 7572 6365 4669 6c65 0100 0b50
6572 736f 6e2e 6a61 7661 0c00 0a00 0b0c
0006 0007 0c00 0800 0901 0016 636f 6d2f
7171 792f 6d61 7064 656d 6f2f 5065 7273
6f6e 0100 106a 6176 612f 6c61 6e67 2f4f
626a 6563 7400 2100 0400 0500 0000 0200
0200 0600 0700 0000 0100 0800 0900 0000
0500 0100 0a00 0b00 0100 0c00 0000 1d00
0100 0100 0000 052a b700 01b1 0000 0001
000d 0000 0006 0001 0000 0008 0001 000e
000f 0001 000c 0000 001d 0001 0001 0000
0005 2ab4 0002 b000 0000 0100 0d00 0000
0600 0100 0000 0e00 0100 1000 1100 0100
0c00 0000 2200 0200 0200 0000 062a 2bb5
0002 b100 0000 0100 0d00 0000 0a00 0200
0000 1200 0500 1300 0100 1200 1300 0100
0c00 0000 1d00 0100 0100 0000 052a b400
03ac 0000 0001 000d 0000 0006 0001 0000
0016 0001 0014 0015 0001 000c 0000 0022
0002 0002 0000 0006 2a1b b500 03b1 0000
0001 000d 0000 000a 0002 0000 001a 0005
001b 0001 0016 0000 0002 0017
除此之外,我們可以通過javap命令來查看Class文件的具體信息
javap -v -c -s -l Person.class
得到的信息如下所示:
Classfile /Users/qin/AndroidStudioProjects/MapDemo/app/src/main/java/com/qqy/mapdemo/Person.class
Last modified 2020-12-21; size 556 bytes
MD5 checksum c03fc3c9928ccc5beba4b52b5aba890e
Compiled from "Person.java"
public class com.qqy.mapdemo.Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#25 // com/qqy/mapdemo/Person.name:Ljava/lang/String;
#3 = Fieldref #4.#26 // com/qqy/mapdemo/Person.age:I
#4 = Class #27 // com/qqy/mapdemo/Person
#5 = Class #28 // java/lang/Object
#6 = Utf8 name
#7 = Utf8 Ljava/lang/String;
#8 = Utf8 age
#9 = Utf8 I
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 getName
#15 = Utf8 ()Ljava/lang/String;
#16 = Utf8 setName
#17 = Utf8 (Ljava/lang/String;)V
#18 = Utf8 getAge
#19 = Utf8 ()I
#20 = Utf8 setAge
#21 = Utf8 (I)V
#22 = Utf8 SourceFile
#23 = Utf8 Person.java
#24 = NameAndType #10:#11 // "<init>":()V
#25 = NameAndType #6:#7 // name:Ljava/lang/String;
#26 = NameAndType #8:#9 // age:I
#27 = Utf8 com/qqy/mapdemo/Person
#28 = Utf8 java/lang/Object
{
public int age;
descriptor: I
flags: ACC_PUBLIC
public com.qqy.mapdemo.Person();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field name:Ljava/lang/String;
4: areturn
LineNumberTable:
line 14: 0
public void setName(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #2 // Field name:Ljava/lang/String;
5: return
LineNumberTable:
line 18: 0
line 19: 5
public int getAge();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #3 // Field age:I
4: ireturn
LineNumberTable:
line 22: 0
public void setAge(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #3 // Field age:I
5: return
LineNumberTable:
line 26: 0
line 27: 5
}
SourceFile: "Person.java"
Class文件結(jié)構(gòu)
??在以上準(zhǔn)備工作做好之后具则,我們來正式開始分析Person.class中十六進制的數(shù)據(jù)是怎么排列的即纲,以及各個部分都是如何表示類信息的。
魔數(shù)
每一個字節(jié)碼文件的開頭都是一個確定的占四個字節(jié)的16進制的數(shù)字博肋,我們把這個叫做魔數(shù)低斋,值為:cafe babe。這個值是確定的束昵,每一個.class文件的開頭都必須是該值拔稳。
Java版本
魔數(shù)之后是Java的版本信息,也是占四個字節(jié)锹雏,其中前兩個字節(jié)是次版本巴比,后兩個字節(jié)是主版本。這里的值是:0000 0034礁遵,轉(zhuǎn)換為10進制轻绞,對應(yīng)的Java版本中,次版本是0佣耐,主版本是52(1.8)政勃,所以Java版本是1.8.0,我們看下本機的Java版本:
java version "1.8.0_131"
常量池
緊接著是常量池兼砖,常量值的所占長度是不確定的奸远,其中前兩個字節(jié)是常量池的長度,001d讽挟,對應(yīng)的十進制是 16 + 13 - 1 = 28懒叛,為什么要減一呢?因為0是被JVM虛擬機占用的耽梅,我們看下被我們通過javap命令得出的字節(jié)碼文件結(jié)構(gòu)中的常量池部分:
Constant pool:
#1 = Methodref #5.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#25 // com/qqy/mapdemo/Person.name:Ljava/lang/String;
#3 = Fieldref #4.#26 // com/qqy/mapdemo/Person.age:I
#4 = Class #27 // com/qqy/mapdemo/Person
#5 = Class #28 // java/lang/Object
#6 = Utf8 name
#7 = Utf8 Ljava/lang/String;
#8 = Utf8 age
#9 = Utf8 I
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 getName
#15 = Utf8 ()Ljava/lang/String;
#16 = Utf8 setName
#17 = Utf8 (Ljava/lang/String;)V
#18 = Utf8 getAge
#19 = Utf8 ()I
#20 = Utf8 setAge
#21 = Utf8 (I)V
#22 = Utf8 SourceFile
#23 = Utf8 Person.java
#24 = NameAndType #10:#11 // "<init>":()V
#25 = NameAndType #6:#7 // name:Ljava/lang/String;
#26 = NameAndType #8:#9 // age:I
#27 = Utf8 com/qqy/mapdemo/Person
#28 = Utf8 java/lang/Object
這部分非常重要薛窥,因為在下面的方法、類信息眼姐、屬性等等的分析 中诅迷,都會用到這部分的內(nèi)容,下面我們花點時間众旗,把上述的28個常量挨個分析一遍罢杉。
分析這部分的內(nèi)容,我們需要用到一個前面提到的表結(jié)構(gòu)說明贡歧,如下圖所示:
常量1
tag值為0a滩租,轉(zhuǎn)換為10進制是10拱镐,因此類型是:CONSTANT_Methodref_info。接著是兩個占兩個字節(jié)的index持际,0005 0018,分別對應(yīng)05#和24#哗咆,我們看下圖3:
#1 = Methodref #5.#24 // java/lang/Object."<init>":()V
至于后面的信息蜘欲,我們稍后再介紹∩渭恚可以看到姥份,通過javap命令得到的信息驗證了我們分信息得出的結(jié)論。
常量2
tag值為09年碘,類型為:CONSTANT_Fieldref_info,接著兩個index澈歉,0004 0019,分別對應(yīng)04#和25#屿衅,表示字段name埃难。
#2 = Fieldref #4.#25 // com/qqy/mapdemo/Person.name:Ljava/lang/String;
常量3
tag值為09,類型為:CONSTANT_Fieldref_info,接著兩個index涤久,0004 001a 涡尘,分別對應(yīng)#04和#26,表示字段age响迂。
#3 = Fieldref #4.#26 // com/qqy/mapdemo/Person.age:I
常量4
tag值為07考抄,類型為:CONSTANT_Class_info,接著是兩個字節(jié)的index,值為:001b,對應(yīng)索引值為:#27蔗彤,表示類信息
#4 = Class #27 // com/qqy/mapdemo/Person
常量5
tag值為07川梅,類型為:CONSTANT_Class_info,接著是兩個字節(jié)的index,值為:001c然遏,對應(yīng)索引值為#28贫途,表示類信息
#5 = Class #28 // java/lang/Object
####### 常量6
tag值為01,類型為:CONSTANT_Utf8_info啦鸣,接著是長度潮饱,占兩個字節(jié):00 04。這里的長度是4诫给,我們往后數(shù)四個字節(jié)的數(shù)據(jù)香拉,6e 61 6d 65,對應(yīng)ASCII表中狂,值為:name凫碌,對應(yīng)字段名稱。
#6 = Utf8 name
常量7
tag值為01胃榕,類型為:CONSTANT_Utf8_info盛险,接著是長度瞄摊,占兩個字節(jié):00 12。這里的長度是18苦掘,我們往后數(shù)18個字節(jié)的數(shù)據(jù)换帜,4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b,對應(yīng)ASCII表鹤啡,值為:Ljava/lang/String;
#7 = Utf8 Ljava/lang/String;
常量8
tag值為01惯驼,類型為:CONSTANT_Utf8_info,接著是長度递瑰,占兩個字節(jié):00 03祟牲。這里的長度是3,我們往后數(shù)3個字節(jié)的數(shù)據(jù)抖部,61 67 65说贝,對應(yīng)ASCII表,值為:age慎颗,對應(yīng)字段名稱乡恕。
#8 = Utf8 age
常量9
tag值為01,類型為:CONSTANT_Utf8_info俯萎,接著是長度几颜,占兩個字節(jié):00 01。這里的長度是1讯屈,我們往后數(shù)1個字節(jié)的數(shù)據(jù)蛋哭,49,對應(yīng)ASCII表涮母,值為:I谆趾,標(biāo)識Integer。
#9 = Utf8 I
常量10
tag值為01叛本,類型為:CONSTANT_Utf8_info沪蓬,接著是長度,占兩個字節(jié):00 06来候。這里的長度6跷叉,我們往后數(shù)6個字節(jié)的數(shù)據(jù),3c 69 6e 69 74 3e 营搅,對應(yīng)ASCII表云挟,值為:<init>,表明是構(gòu)造方法转质。
#10 = Utf8 <init>
常量11
tag值為01园欣,類型為:CONSTANT_Utf8_info,接著是長度休蟹,占兩個字節(jié):00 03沸枯。這里的長度6日矫,我們往后數(shù)3個字節(jié)的數(shù)據(jù):28 29 56 ,對應(yīng)ASCII表绑榴,值為:()V哪轿,表明構(gòu)造方法的沒有入?yún)ⅲ祷刂凳荲oid翔怎。這是JVM默認(rèn)給加的默認(rèn)的構(gòu)造方法缔逛。
常量12
tag值為01,類型為:CONSTANT_Utf8_info姓惑,接著是長度,占兩個字節(jié):00 04按脚。這里的長度4于毙,我們往后數(shù)4個字節(jié)的數(shù)據(jù):43 6f 64 65 ,對應(yīng)ASCII表辅搬,值為:Code唯沮。這里的Code很重要,在Code里面存放的是JVM指令集堪遂。
#12 = Utf8 Code
常量13
tag值為01介蛉,類型為:CONSTANT_Utf8_info,接著是長度溶褪,占兩個字節(jié):00 0f币旧。這里的長度15,我們往后數(shù)15個字節(jié)的數(shù)據(jù):4c 69
6e 65 4e 75 6d 62 65 72 54 61 62 6c 65猿妈,對應(yīng)ASCII表吹菱,值為:LineNumberTable。代表的意思是行號表彭则,表示JVM指令和Java代碼的映射關(guān)系鳍刷,JVM在執(zhí)行指令的時候,如果有異掣┒叮可以精準(zhǔn)定位到Java代碼中输瓜,是因為這個。
#13 = Utf8 LineNumberTable
常量14
tag值為01芬萍,類型為:CONSTANT_Utf8_info尤揣,接著是長度,占兩個字節(jié):00 07柬祠。這里的長度7芹缔,我們往后數(shù)7個字節(jié)的數(shù)據(jù):67 65 74 4e 61 6d 65,對應(yīng)ASCII表瓶盛,值為:getName最欠,表示方法名示罗。
#14 = Utf8 getName
常量15
tag值為01,類型為:CONSTANT_Utf8_info芝硬,接著是長度蚜点,占兩個字節(jié):00 14。這里的長度20拌阴,我們往后數(shù)20個字節(jié)的數(shù)據(jù):28 29 4c 6a 61 76
61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 绍绘,對應(yīng)ASCII表,值為:()Ljava/lang/String;迟赃,表示方法沒有入?yún)⑴憔校祷刂凳荢tring類型。
#15 = Utf8 ()Ljava/lang/String;
常量16
tag值為01纤壁,類型為:CONSTANT_Utf8_info左刽,接著是長度,占兩個字節(jié):00 07酌媒。這里的長度7欠痴,我們往后數(shù)7個字節(jié)的數(shù)據(jù):07 73 65 74 4e 61 6d 65 ,對應(yīng)ASCII表秒咨,值為:setName喇辽,表示setName方法。
#16 = Utf8 setName
常量17
tag值為01雨席,類型為:CONSTANT_Utf8_info菩咨,接著是長度,占兩個字節(jié):00 15陡厘。這里的長度21旦委,我們往后數(shù)21個字節(jié)的數(shù)據(jù):28 4c 6a 61 76
61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 ,對應(yīng)ASCII表雏亚,值為:(Ljava/lang/String;)V缨硝。這里表示方法的入?yún)⑹荢tring類型,返回值是Void類型罢低。
#17 = Utf8 (Ljava/lang/String;)V
常量18
tag值為01查辩,類型為:CONSTANT_Utf8_info,接著是長度网持,占兩個字節(jié):00 06宜岛。這里的長度6,我們往后數(shù)6個字節(jié)的數(shù)據(jù):67 65 74 41 67 65 功舀,對應(yīng)ASCII表萍倡,值為:getAge,表示getAge方法辟汰。
#18 = Utf8 getAge
常量19
tag值為01列敲,類型為:CONSTANT_Utf8_info,接著是長度凑术,占兩個字節(jié):00 03淮逊。這里的長度3扶踊,我們往后數(shù)3個字節(jié)的數(shù)據(jù):28 29 49 ,對應(yīng)ASCII表备籽,值為:()I,表示方法沒有入?yún)⑿灏妫祷刂凳荌nteger類型。
#19 = Utf8 ()I
常量20
tag值為01歼疮,類型為:CONSTANT_Utf8_info杂抽,接著是長度,占兩個字節(jié):00 06韩脏。這里的長度6缩麸,我們往后數(shù)6個字節(jié)的數(shù)據(jù):73 65 74 41 67 65 ,對應(yīng)ASCII表赡矢,值為:setAge杭朱,表示setAge方法。
#20 = Utf8 setAge
常量21
tag值為01吹散,類型為:CONSTANT_Utf8_info弧械,接著是長度,占兩個字節(jié):00 04空民。這里的長度4刃唐,我們往后數(shù)4個字節(jié)的數(shù)據(jù):28 49 29 56,對應(yīng)ASCII表界轩,值為:(I)V画饥,表示方法的入?yún)⑹荌nteger類型,返回值是Void浊猾。
#21 = Utf8 (I)V
常量22
tag值為01抖甘,類型為:CONSTANT_Utf8_info薇宠,接著是長度,占兩個字節(jié):00 0a慢睡。這里的長度10,我們往后數(shù)10個字節(jié)的數(shù)據(jù):53 6f 75 72 63 65 46 69 6c 65 髓涯,對應(yīng)ASCII表,值為:SourceFile包各。表示源文件。
#22 = Utf8 SourceFile
常量23
tag值為01护姆,類型為:CONSTANT_Utf8_info,接著是長度渐裂,占兩個字節(jié):00 0b柒凉。這里的長度11,我們往后數(shù)11個字節(jié)的數(shù)據(jù):50 65 72 73 6f 6e 2e 6a 61 76 61 蔬咬,對應(yīng)ASCII表盖奈,值為:Person.java。表示源文件是Persion.java爹凹。
#23 = Utf8 Person.java
常量24
tag為0c,類型為:CONSTANT_NameAndType_info颤陶,接著是長度,占四個字節(jié)闲坎,值為:00 0a 00 0b 。表示是#10和#11。
#24 = NameAndType #10:#11 // "<init>":()V
常量25
tag為0c娄蔼,類型為:CONSTANT_NameAndType_info,接著是長度涕癣,占四個字節(jié)距潘,值為:00 06 00 07。表示#06和#07洞翩。
#25 = NameAndType #6:#7 // name:Ljava/lang/String;
常量26
tag為0c,類型為:CONSTANT_NameAndType_info循未,接著是長度的妖,占四個字節(jié),值為:00 08 00 09星虹。表示#08和#09。
#26 = NameAndType #8:#9 // age:I
常量27
tag值為01卸亮,類型為:CONSTANT_Utf8_info吃溅,接著是長度螺垢,占兩個字節(jié):00 16蹂楣。這里的長度22墨林,我們往后數(shù)22個字節(jié)的數(shù)據(jù):63 6f 6d 2f
71 71 79 2f 6d 61 70 64 65 6d 6f 2f 50 65 72 73
6f 6e 酌呆,對應(yīng)ASCII表,值為:com/qqy/mapdemo/Person。表示類所在的Package娜饵。
#27 = Utf8 com/qqy/mapdemo/Person
常量28
tag值為01,類型為:CONSTANT_Utf8_info晴股,接著是長度队魏,占兩個字節(jié):00 10瞬雹。這里的長度16,我們往后數(shù)16個字節(jié)的數(shù)據(jù):6a 61 76 61 2f 6c 61 6e 67 2f 4f
62 6a 65 63 74 尚镰,對應(yīng)ASCII表分俯,值為:java/lang/Object缸剪。
#28 = Utf8 java/lang/Object
到這里,常量池的部分已經(jīng)分析完了东亦。在分析每一個常量的時候杏节,各自代表的意思已經(jīng)說得很清楚了,這里就不再一一贅述了典阵。
Access Flags
通過字節(jié)碼結(jié)構(gòu)圖我們知道拢锹,常量池下面是Access Flags,即訪問標(biāo)志充坑,占兩個字節(jié)司志,值為:00 21⊙构蹋看下面一張圖:
我們可以看到,圖里面并沒有Ox0021福稳,這是因為該值是0x0020和0x0001的并集梅掠,即ACC_PUBLIC和ACC_SUPER肄满。這里標(biāo)識該類是public的勺疼,并且可以訪問父類。
This Class Name
訪問標(biāo)識之后是類名,占兩個字節(jié)语婴,值為:00 04僻造。這里表示指向常量池中#04的信息坎怪,我們看上面的常量池的#4值,為:com/qqy/mapdemo/Person挂签。
Super Class Name
This Class Name后面是Super Class Name芹关,即父類的名字博个。占兩個字節(jié),值為:00 05一铅。這里標(biāo)識指向常量池中#05的信息,我們看上面的常量池的#05的值
#5 = Class #28 // java/lang/Object
值為:java/lang/Object潘飘。
Interfaces
接口肮之,這里占的2 + n個字節(jié)掉缺,包括兩部分,第一部分是interfaces_count(接口的個數(shù))戈擒,第二部分是interfaces(接口名)眶明。我們來看第一部分的兩個字節(jié),值為:00 00筐高。表示接口個數(shù)為0搜囱,因此該類沒有實現(xiàn)任何接口,所以下面的interfaces就不存在了柑土。如果count大于0的話蜀肘,那接口表是存在的。有興趣的同學(xué)可以自行去實現(xiàn)一下看卡稽屏。
我們知道扮宠,在Java中,一個類最多實現(xiàn)的接口個數(shù)是65535狐榔,那么這個65535是怎么來的呢坛增?答案就在這里,接口個數(shù)占2個字節(jié)薄腻,也就是16位收捣,如果16位全部是1,那就是65535被廓,所以最大值是65535坏晦。
Fields
字段表。跟上面的接口一樣嫁乘,也是占2 + n個字節(jié)昆婿,包括兩部分。其中第一部分是字段表的長度蜓斧,占兩個字節(jié)仓蛆,值為:00 02笤闯,表示有兩個字段逾冬。這里就是我們的name字段和age字段了。
后面接跟著是filed_info[],是一個描述字段的數(shù)組浦旱。filed_info的結(jié)構(gòu)是什么樣呢直奋?如下圖所示:
再來看下字段訪問標(biāo)志能庆,如下圖:
如圖4所示,每一個字段需要訪問標(biāo)志脚线、字段名稱和字段類型來唯一確定搁胆。 attribute_count標(biāo)識屬性表長度,attribute_info表示字段屬性。
我們接著分析渠旁,字段的訪問標(biāo)志占兩個字節(jié)攀例,我們接著數(shù)兩個字節(jié):0002。通過圖5我們可以得到0x0002代表的是ACC_PRIVATE顾腊,是私有的粤铭。
接著往下,下面是字段名稱索引杂靶,占兩個字節(jié)梆惯,值為:00 06,都應(yīng)常量池中的#06伪煤,我們?nèi)ド厦娉A砍刂锌梢灾溃?06對應(yīng)的是name加袋,即字段名稱是name凛辣。
再接著往下抱既,是字段索引描述,同樣占兩個字符扁誓,值為:00 07防泵,對應(yīng)的是#07,查常量值知道蝗敢,對應(yīng)的是:Ljava/lang/String捷泞,即字段是String類型的。
接著再往下寿谴,屬性表個數(shù)锁右,占兩個字節(jié),值為:00 00讶泰。因此沒有屬性表咏瑟。該字段到此就結(jié)束了』臼穑總結(jié)起來就是码泞,私有的、類型為String類型的狼犯、名稱為name的字段余寥,即我們java代碼中的:
private String name;
接著往下,下一個字段的訪問標(biāo)志位占兩個字節(jié)悯森, 00 01宋舷,查表為:ACC_PUBLIC,表示公有。
接著兩個字節(jié)為字段名稱索引瓢姻,占兩個字節(jié)祝蝠,值為:00 08,對應(yīng)的是#08,查常量值知道续膳,對應(yīng)的是:age改艇。表示字段名稱是age。
再往下兩個字節(jié)為字段的索引描述坟岔,同樣占兩個字符谒兄,值為:00 09,對應(yīng)的是#9社付,查常量值知道承疲,對應(yīng)的是:I,即為int類型鸥咖。
再往下兩個字節(jié)燕鸽,表示字段屬性長度,值為:00 00啼辣。所以這里沒有屬性啊研。該字段到此結(jié)束,綜上為:
private int age;
Methods
方法鸥拧,占用2 + n個字節(jié)党远,其中前兩個字節(jié)表示方法的個數(shù),值為:00 05富弦。說明有五個方法沟娱,我們來數(shù)一下:setName/getName/setAge/getAge/構(gòu)造方法,正好五個腕柜。接下來是方法表济似,方法表的結(jié)構(gòu)比較復(fù)雜,我們來一一看下盏缤。
老規(guī)矩砰蠢,先來看下方法表結(jié)構(gòu):
接著再來看下,方法的訪問標(biāo)志:
方法-1
前兩個字節(jié)為方法訪問權(quán)限蛾找,占兩個字節(jié)娩脾,值為:00 01,查表可知打毛,為:ACC_PUBLIC柿赊,表示公有的。
接下來的兩個字節(jié)表示方法名稱的索引幻枉,值為00 0a碰声,十進制為10,看下常量池中#10對應(yīng)的值為 <init>熬甫,表示構(gòu)造方法胰挑。
接著看,還是兩個字節(jié),表示方法描述索引瞻颂,值為:00 0b豺谈,十進制為11,看下常量池中#11對應(yīng)的值為:()V贡这,說明構(gòu)造方法是無入?yún)⒉缒祷刂礦oid。
再往后的兩個字節(jié)表示方法的屬性個數(shù)盖矫,值為:00 01丽惭,表示有一個屬性。
接下來看屬性信息辈双,先看下屬性表結(jié)構(gòu):
兩個字節(jié)责掏,表示屬性的名稱指向常量池的索引,值為:00 0c湃望,十進制為12换衬,看下常量池對應(yīng)的#12的數(shù)據(jù)為Code。這個Code很重要喜爷,表示字節(jié)碼指令的信息冗疮,說明此屬性是方法的字節(jié)碼描述萄唇。
接下來的四個字節(jié)檩帐,為Code內(nèi)容的長度,值為:00 00 00 1d另萤,十進制為29湃密,表示Code長度為29。詳情為:
00 01 00 01 00 00 00 05 2a b7 00 01 b1
00 00 00 01 00 0d 00 00 00 06 00 01 00 00 00 08
我們看下Code對應(yīng)的屬性結(jié)構(gòu):
上面我們已經(jīng)分析了attribute_name_index和attribute_length四敞,因此Code長度的29個字節(jié)是從max_stack開始的泛源。
max_stack,方法最大操作數(shù)棧的深度忿危,占兩個字節(jié)达箍,值為:00 01,十進制為1铺厨,表示最大操作數(shù)棧的深度為1缎玫。
max_locals,方法的局部變量表的個數(shù)解滓,占兩個字節(jié)赃磨,值為00 01,十進制為1洼裤,表示局部變量表的個數(shù)為1邻辉。
Code_length,指令碼的長度,表示有該方法對應(yīng)多少個指令值骇。占四個字節(jié)莹菱,值為:00 00 00 05,十進制為5吱瘩,說明有五個指令碼芒珠。
Code[Code_length],具體指令碼搅裙,因為有五個指令碼皱卓,所以長度為5個字節(jié),值為:2a b7 00 01 b1部逮。
編號 | 具體指令(助記符) |
---|---|
0 | aload_0 |
1 | invokespecial #1 |
4 | return |
這里需要說明一下娜汁,機器能夠認(rèn)識的是指令碼,也就是我們拿到的十六進制的數(shù)據(jù)兄朋,至于上述的aload_0或者invokespecial等等掐禁,我們稱之為助記符。
先來來看下颅和,這個指令碼對應(yīng)的分別是什么傅事。
這里給大家推薦一個插件,名字叫Jclasslib峡扩,通過這個插件我們可以看到具體的指令信息蹭越。如圖所示:
在紅框中的信息我們可以點擊,會自動打開瀏覽器教届,我們看下具體信息:
可以看到响鹃,aload_0對應(yīng)的十六進制是0x2a。接著往下看案训,點擊invokespecial:
可以看到买置,invokespecial對應(yīng)的十六進制是0xb7。invokespecial的意思是調(diào)用父類的構(gòu)造方法强霎,那么具體是哪個父類的忿项?這就是b7后面后面跟著的00 01決定的,00 01對應(yīng)的常量池索引是#1城舞,我們看下轩触,在常量池中#1對應(yīng)的是java/lang/Object <init>:()V,這里的意思就是Object類的構(gòu)造方法椿争。
我們再點開return看一下:
可以看到怕膛,return對應(yīng)的值是0xb1。
虛擬機在讀到字節(jié)碼區(qū)域的長度后秦踪,按照順序依次讀入緊隨的5個字節(jié)褐捻,并根據(jù)字節(jié)碼指令表翻譯出所對應(yīng)的字節(jié)碼指令掸茅。翻譯"2A B7 00 0A B1"的過程為:
1.讀入2A,查表得0x2A對應(yīng)的指令為aload_0柠逞,這個指令的含義是將第0個Slot中為reference類型的本地變量推送到操作數(shù)棧頂昧狮。
2.讀入B7,查表得B7對應(yīng)的指令為invokespecial板壮,這條指令的作用是以棧頂?shù)膔eference類型的數(shù)據(jù)所指向的對象作為方法接收者逗鸣,調(diào)用此對象的實例構(gòu)造器方法、private方法或者它的父類的方法绰精。這個方法有一個u2類型的參數(shù)說明具體調(diào)用哪一個方法撒璧。
3.讀入00 0A,這是invokespecial的參數(shù)笨使,查常量池可以為實力構(gòu)造器<init>方法的符號引用卿樱。
4.讀入B1,查表得0xB1對應(yīng)的指令為return硫椰,含義是返回此方法繁调,并且返回值為Void。這條指令執(zhí)行后靶草,當(dāng)前方法結(jié)束蹄胰。
接下來是異常表長度,占兩個字節(jié)奕翔,值為:00 00裕寨,長度是0,表示該方法不會拋出異常糠悯。
接著往下帮坚,兩個字節(jié)00 01表示屬性表長度為1。
兩個字節(jié)是常量池索引互艾,00 0d,十進制為13讯泣,我們看下具體值為:LineNumberTable纫普,表示字節(jié)碼指令和java代碼行數(shù)的映射。下圖表示LineNumberTable的屬性結(jié)構(gòu):
在圖中的line_number_table是一個數(shù)量為line_number_table_length好渠、類型為line_number_info的集合昨稼,line_number_info表包括了start_pc和line_number兩個u2類型的數(shù)據(jù)項,前者是字節(jié)碼行號拳锚,后者是Java源碼行號假栓。因此我們可以得出,在00 0d之后的兩個字節(jié)表示的是line_number_table_length霍掺,值為:00 01匾荆,表示line_number_table的長度為1拌蜘,即00 00 代表字節(jié)碼行號, 00 08代表Java源碼行號牙丽。
到這里简卧,構(gòu)造方法已經(jīng)分析結(jié)束了。這里要注意一點的是烤芦,javap輸出的arg_size的值可能會有疑問举娩,構(gòu)造方法是沒有入?yún)⒌陌。@里的長度怎么會是1呢构罗?而且不管是在參數(shù)列表里面還是方法體內(nèi)铜涉,都沒有定義任何局部變量,那locals怎么也是1呢遂唧?這是因為骄噪,在任何實例方法里面,都可以通過this關(guān)鍵字訪問到此方法所屬的對象蠢箩。這個訪問機制對Java程序的編寫很重要链蕊,而它的實現(xiàn)卻非常簡單,僅僅是通過javac編譯器編譯的時候把對this關(guān)鍵字的訪問轉(zhuǎn)變?yōu)閷σ粋€普通方法參數(shù)的訪問谬泌,然后在虛擬機調(diào)用實例方法時自動傳入此參數(shù)而已滔韵。因此在實例方法的局部變量表中至少會存在一個指向當(dāng)前對象實例的局部變量,局部變量表中也會預(yù)留出第一個Slot位來存放對象實例的引用掌实。
方法-2
第二個方法為getName陪蜻,我們看下getName的javap信息:
前兩個字節(jié)00 01表示方法訪問標(biāo)志,查表可知為ACC_PUBLIC贱鼻,對應(yīng)為public宴卖。
接下來,00 0e邻悬,十進制為14症昏,代表常量池索引#14,值為:getName父丰。
00 0f肝谭,表示方法描述的索引,十進制為15蛾扇,代表常量池#15攘烛,值為:()Ljava/lang/String;,表示方法沒有入?yún)⒍剖祝祷刂凳荢tring坟漱。
接下來是:00 01,代表方法的屬性個數(shù)更哄,值為1芋齿。說明只有一個屬性腥寇。
接下來的兩個字節(jié),00 0c沟突,表示屬性的名稱指向常量池的索引花颗,十進制為12,看一下常量池#12惠拭,值為:Code扩劝。表示的是Code屬性。
接下來的四個字節(jié)表示Code內(nèi)容的長度职辅,值為:00 00 00 1d棒呛,十進制為:29。
這里把數(shù)據(jù)直接列到這里域携,方便下面查看:00 01 00 01 00 00 00 05 2a b4 00 02 b0 00 00 00 01 00 0d 00 00 00 06 00 01 00 00 00 0e
00 01 表示方法的最大操作數(shù)棧的深度簇秒,值為1
00 01 表示局部變量表的個數(shù),值為1
00 00 00 05 表示指令碼的長度秀鞭,為5
2a b4 00 02 b0為指令碼趋观,其中2a表示aload_0,b4表示getField锋边,其中00 02表示常量池中#2皱坛,值為:name。areturn表示返回常量池中的#2對應(yīng)的值豆巨,即返回name剩辟。
00 00表示異常信息的大小是0,因此沒有異常信息往扔。
00 01表示屬性表的大小是1
00 0d表示是常量池中的#13贩猎,值為:LineNumberTable。
接著的四個字節(jié)是屬性的長度萍膛,00 00 00 06吭服,表示長度為6。
接著的兩個字節(jié)表示行號表的長度卦羡,00 01噪馏,長度為1。
接下來的四個字節(jié)绿饵,前兩個字節(jié)代表字節(jié)碼行號,后兩個自己代表Java源碼行號瓶颠。00 00表示aload_0拟赊,00 0e表示Java類中的第14行
return name;
方法-3
第三個方法是setName,我們看下setName的javap信息:
前兩個字節(jié)00 01表示方法訪問標(biāo)志粹淋,查表可知為ACC_PUBLIC吸祟,對應(yīng)為public瑟慈。
接下來的兩個字節(jié)00 10表示方法名字,對應(yīng)常量池中的#16屋匕,為:setName葛碧。
再往下兩個字節(jié)00 11表示方法描述索引,對應(yīng)常量池中#17过吻,為: (Ljava/lang/String;)V进泼,表示方法的入?yún)⑹荢tring類型,返回值為Void纤虽。
再往下兩個字節(jié)00 01表示屬性表的長度乳绕,值為1。
接下來的兩個字節(jié)00 0c表示屬性值的索引逼纸,對應(yīng)12洋措,值為:Code。說明這唯一的一個屬性是Code屬性杰刽,接下來轉(zhuǎn)到Code屬性表中菠发。
再往下四個字節(jié)表示Code屬性表的長度,值為:00 00 00 22贺嫂,轉(zhuǎn)為十進制為:2 * 16 + 2 = 34滓鸠,往后數(shù)34個字節(jié),該方法結(jié)束涝婉。值為:
00 02 00 02 00 00 00 06 2a 2b b5 00 02 b1 00 00 00
01 00 0d 00 00 00 0a 00 02 00 00 00 12 00 05 00 13
00 02為max_stack哥力,表示方法的最大操作數(shù)棧的深度,值為2
00 02位max_locals墩弯,表示局部變量表的個數(shù)吩跋,值為2
00 00 00 06表示字節(jié)碼指令的個數(shù),長度為6渔工,為:2a 2b b5 00 02 b1锌钮。對應(yīng):
編號 | 具體指令(助記符) |
---|---|
0 | aload_0 |
1 | aload_1 |
2 | putfield #2 <com/qqy/mapdemo/Person.name> |
5 | return |
接下來的00 00,表示異常表的個數(shù)是0引矩,跳過梁丘。
00 01表示屬性表的大小為1,再往下取兩個字節(jié)00 0d旺韭,表示具體的屬性名稱氛谜,對應(yīng)常量池中的#13,為:LineNumberTable区端。
接下來轉(zhuǎn)到LineNumberTable屬性結(jié)構(gòu)中值漫。
00 00 00 0a 00 02 00 00 00 12 00 05 00 13
00 00 00 0a表示屬性長度,值為:10织盼,正好對應(yīng)后面的10個字節(jié)杨何。
00 02表示line_number_table_length酱塔,屬性個數(shù)為2,對應(yīng)關(guān)系為
字節(jié)碼 | Java源碼行數(shù) |
---|---|
00 00-0 | 00 12-18 |
00 05-5 | 00 13-19 |
方法-4
第四個方法是getAge危虱,我們看下getAge的javap信息:
前兩個字節(jié)00 01表示方法訪問標(biāo)志羊娃,查表可知為ACC_PUBLIC,對應(yīng)為public埃跷。
接下來的兩個字節(jié)00 12表示方法名字蕊玷,對應(yīng)常量池中的#18,為:getAge捌蚊。
再往下兩個字節(jié)00 13表示方法描述索引集畅,對應(yīng)常量池中#19,為: ()I
缅糟,表示方法沒有入?yún)⑼χ牵祷刂禐閕nt類型。
再往下兩個字節(jié)00 01表示屬性表的長度窗宦,值為1赦颇。
接下來的兩個字節(jié)00 0c表示屬性值的索引,對應(yīng)12赴涵,值為:Code媒怯。說明這唯一的一個屬性是Code屬性,接下來轉(zhuǎn)到Code屬性表中髓窜。
再往下四個字節(jié)表示Code屬性表的長度扇苞,值為:00 00 00 1d,轉(zhuǎn)為十進制為:1 * 16 + 13 = 29寄纵,往后數(shù)29個字節(jié)鳖敷,該方法結(jié)束。值為:
00 01 00 01 00 00 00 05 2a b4 00 03 ac
00 00 00 01 00 0d 00 00 00 06 00 01 00 00 00 16
00 01為max_stack程拭,表示方法的最大操作數(shù)棧的深度定踱,值為1
00 01位max_locals,表示局部變量表的個數(shù)恃鞋,值為1
00 00 00 05表示字節(jié)碼指令的個數(shù)崖媚,長度為5,為:2a b4 00 03 ac 恤浪。對應(yīng):
編號 | 具體指令(助記符) |
---|---|
0 | aload_0 |
1 | getfield #3 <com/qqy/mapdemo/Person.age> |
4 | ireturn |
接下來的00 00畅哑,表示異常表的個數(shù)是0,跳過水由。
00 01表示屬性表的大小為1敢课,再往下取兩個字節(jié)00 0d,表示具體的屬性名稱绷杜,對應(yīng)常量池中的#13直秆,為:LineNumberTable。
接下來轉(zhuǎn)到LineNumberTable屬性結(jié)構(gòu)中鞭盟。
00 00 00 06 00 01 00 00 00 16
00 00 00 06表示屬性長度圾结,值為:6,正好對應(yīng)后面的6個字節(jié)齿诉。
00 01表示line_number_table_length筝野,屬性個數(shù)為1,對應(yīng)關(guān)系為
字節(jié)碼 | Java源碼行數(shù) |
---|---|
00 00-0 | 00 16-22 |
方法-5
第三個方法是setAge粤剧,我們看下setAge的javap信息:
前兩個字節(jié)00 01表示方法訪問標(biāo)志歇竟,查表可知為ACC_PUBLIC,對應(yīng)為public抵恋。
接下來的兩個字節(jié)00 14表示方法名字焕议,對應(yīng)常量池中的#20,為:setAge弧关。
再往下兩個字節(jié)00 15表示方法描述索引盅安,對應(yīng)常量池中#21,為: (I)V世囊,表示方法的入?yún)⑹莍nt類型果漾,返回值為Void美莫。
再往下兩個字節(jié)00 01表示屬性表的長度,值為1。
接下來的兩個字節(jié)00 0c表示屬性值的索引恩商,對應(yīng)12,值為:Code欧引。說明這唯一的一個屬性是Code屬性酸役,接下來轉(zhuǎn)到Code屬性表中。
再往下四個字節(jié)表示Code屬性表的長度猫胁,值為:00 00 00 22箱亿,轉(zhuǎn)為十進制為:2 * 16 + 2 = 34,往后數(shù)34個字節(jié)弃秆,該方法結(jié)束届惋。值為:
00 02 00 02 00 00 00 06 2a 1b b5 00 03 b1 00 00 00
01 00 0d 00 00 00 0a 00 02 00 00 00 1a 00 05 00 1b
00 02為max_stack,表示方法的最大操作數(shù)棧的深度菠赚,值為2
00 02位max_locals脑豹,表示局部變量表的個數(shù),值為2
00 00 00 06表示字節(jié)碼指令的個數(shù)衡查,長度為6瘩欺,為:2a 1b b5 00 03 b1。對應(yīng):
編號 | 具體指令(助記符) |
---|---|
0 | aload_0 |
1 | iload_1 |
2 | putfield #3 <com/qqy/mapdemo/Person.age> |
5 | return |
接下來的00 00,表示異常表的個數(shù)是0俱饿,跳過歌粥。
00 01表示屬性表的大小為1,再往下取兩個字節(jié)00 0d拍埠,表示具體的屬性名稱失驶,對應(yīng)常量池中的#13,為:LineNumberTable枣购。
接下來轉(zhuǎn)到LineNumberTable屬性結(jié)構(gòu)中嬉探。
00 00 00 0a 00 02 00 00 00 1a 00 05 00 1b
00 00 00 0a表示屬性長度,值為:10棉圈,正好對應(yīng)后面的10個字節(jié)涩堤。
00 02表示line_number_table_length,屬性個數(shù)為2分瘾,對應(yīng)關(guān)系為
字節(jié)碼 | Java源碼行數(shù) |
---|---|
00 00-0 | 00 1a-26 |
00 05-5 | 00 1b-27 |
到這里胎围,五個方法已經(jīng)全部分析完成了。重新回到Class類結(jié)構(gòu)表中芹敌,可以看到接下來的兩個字節(jié)表示attributes_count痊远,值為:00 01,表示還有一個屬性氏捞。
再往下看兩個字節(jié)碧聪,00 16,對應(yīng)的是常量池中的#22液茎,值為:SourceFile逞姿。回到屬性表結(jié)構(gòu)圖8中查看捆等,接下來四個字節(jié)表示屬性的長度滞造,00 00 00 02表示長度為2個字節(jié),值為:00 17栋烤,對應(yīng)從常量表中的#23谒养,值為:Person.java。
結(jié)語
??到這里明郭,這篇文章就要結(jié)束了买窟。作為字節(jié)碼分析的第一篇文章,本文給大家介紹了整體Class文件結(jié)構(gòu)薯定,以及結(jié)構(gòu)表中的每一部分代表了什么始绍,并通過一個簡單的Person類帶大家進行了一次整體的分析。當(dāng)然了话侄,字節(jié)碼文件中還有許多是我們沒有講到的亏推,比如:我們在Person類中的方法中学赛,異常信息都是 00 00,那如果有異常的情況是什么呢吞杭?再比如盏浇,我們遇到的屬性都是Code屬性,那么其他的屬性呢篇亭?等等......這些問題缠捌,會在后續(xù)的文章中逐一為大家講解。
bye~
參考資料
深入理解Java虛擬機-JVM高級特性與最佳實踐(第2版)