本文將通過對一個簡單的Java源文件可训,編譯成的class文件進(jìn)行分析绘梦,以探索Java class文件的格式嘀掸。
package ex3;
public class ConstantTest {
int a;
public void inc() {
++a;
}
}
將這個Java代碼保存為ConstantTest.java
,使用javac ConstantTest.java
实幕,將其編譯成ConstantTest.class
字節(jié)碼文件吝镣。使用十六進(jìn)制查看器hexdump,查看其內(nèi)容hexdump -C ConstantTest.class
±ケ樱現(xiàn)在看不懂沒關(guān)系末贾,下面我們逐字節(jié)分析。
00000000 ca fe ba be 00 00 00 34 00 12 0a 00 04 00 0e 09 |.......4........|
00000010 00 03 00 0f 07 00 10 07 00 11 01 00 01 61 01 00 |.............a..|
00000020 01 49 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |.I...<init>...()|
00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e |V...Code...LineN|
00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 03 69 6e 63 |umberTable...inc|
00000050 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 11 |...SourceFile...|
00000060 43 6f 6e 73 74 61 6e 74 54 65 73 74 2e 6a 61 76 |ConstantTest.jav|
00000070 61 0c 00 07 00 08 0c 00 05 00 06 01 00 10 65 78 |a.............ex|
00000080 33 2f 43 6f 6e 73 74 61 6e 74 54 65 73 74 01 00 |3/ConstantTest..|
00000090 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 |.java/lang/Objec|
000000a0 74 00 21 00 03 00 04 00 00 00 01 00 00 00 05 00 |t.!.............|
000000b0 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 |................|
000000c0 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 |...........*....|
000000d0 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 00 03 |................|
000000e0 00 01 00 0b 00 08 00 01 00 09 00 00 00 27 00 03 |.............'..|
000000f0 00 01 00 00 00 0b 2a 59 b4 00 02 04 60 b5 00 02 |......*Y....`...|
00000100 b1 00 00 00 01 00 0a 00 00 00 0a 00 02 00 00 00 |................|
00000110 06 00 0a 00 07 00 01 00 0c 00 00 00 02 00 0d |...............|
0000011f
先看看class文件的反匯編內(nèi)容javap -v ContantTest.class
整吆。
public class ex3.ConstantTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#15 // ex3/ConstantTest.a:I
#3 = Class #16 // ex3/ConstantTest
#4 = Class #17 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 inc
#12 = Utf8 SourceFile
#13 = Utf8 ConstantTest.java
#14 = NameAndType #7:#8 // "<init>":()V
#15 = NameAndType #5:#6 // a:I
#16 = Utf8 ex3/ConstantTest
#17 = Utf8 java/lang/Object
{
int a;
descriptor: I
flags:
public ex3.ConstantTest();
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 3: 0
public void inc();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field a:I
5: iconst_1
6: iadd
7: putfield #2 // Field a:I
10: return
LineNumberTable:
line 6: 0
line 7: 10
}
class文件結(jié)構(gòu)
Class文件是一組以8bit字節(jié)為基礎(chǔ)單位的二進(jìn)制流拱撵, 各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在文件之中,中間沒有添加任何分隔符掂为,這使得整個Class文件中存儲的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù)裕膀,沒有空隙存在员串。 當(dāng)遇到需要占用8bit以上空間的數(shù)據(jù)項時勇哗,則會按照高位在前(大端序)的方式分割成若干個字節(jié)進(jìn)行存儲。
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
Class文件格式可以用上面這個結(jié)構(gòu)體來表示寸齐,其中的u2/u4
表示2字節(jié)和4字節(jié)的數(shù)據(jù)欲诺。我們將其分為以下幾個部分來探討。
- magic
- versioin
- 常量池
- access_flags
- 類索引渺鹦、父類索引與接口索引集合
- 字段表
- 方法表
- 屬性表
magic
magic
是一個四字節(jié)的數(shù)據(jù)扰法,它唯一的功能是確定這個文件是否為一個能被虛擬機(jī)接受的Class文件,它的值是固定的0xCAFEBABE
毅厚,是Cafe Babe
的意思塞颁,這是Java在設(shè)計之初就已經(jīng)決定了的。
使用hexdump -C XXX.class | head -n 2
可以查看class文件的十六進(jìn)制表示,可以看到它的前4字節(jié)是ca fe ba be祠锣。
00000000 ca fe ba be 00 00 00 34 00 12 0a 00 04 00 0e 09 |.......4........|
注意:大端序與我們的閱讀順序相同酷窥。例如0x12345678中12是高位字節(jié),78是低位字節(jié)伴网,而class文件中的第1個字節(jié)是低位地址蓬推,第4個字節(jié)是高位地址,大端序中高位字節(jié)放在低位地址澡腾,即12放在第1個字節(jié)沸伏,78放在第4個字節(jié)。如果是小端序动分,就要反過來毅糟,在文件中看到的就是78 56 45 12。
Linux中的file命令可以用來判斷文件類型澜公,它可以利用這個magic
來判斷一個文件是不是class文件留特。
version
緊接著magic
的4個字節(jié)存儲的是Class文件的版本號: 第5和第6個字節(jié)是minor_version
, 第7和第8個字節(jié)是major_version
玛瘸。
Java的版本號是從45開始的蜕青, JDK 1.1之后的每個JDK大版本發(fā)布主版本號向上加1( JDK 1.0~1.1使用了45.0~45.3的版本號),高版本的JDK能向下兼容以前版本的Class文件糊渊, 但不能運(yùn)行以后版本的Class文件右核, 因?yàn)椤?Java虛擬機(jī)規(guī)范》 在Class文件校驗(yàn)部分明確要求了即使文件格式并未發(fā)生任何變化, 虛擬機(jī)也必須拒絕執(zhí)行超過其版本號的Class文件渺绒。
在上面的例子中贺喝,minor_version
為00 00,而major_version
為00 34即52宗兼,因此該class文件版本號為52.0躏鱼,是由JDK 8編譯而來。
關(guān)于次版本號殷绍, 曾經(jīng)在現(xiàn)代Java( 即Java 2)出現(xiàn)前被短暫使用過染苛, JDK 1.0.2支持的版本45.0~45.3( 包括45.0~45.3),JDK 1.1支持版本45.0~45.65535主到, 從JDK 1.2以后茶行, 直到JDK 12之前次版本號均未使用, 全部固定為零登钥。 而到了JDK 12時期畔师, 由于JDK提供的功能集已經(jīng)非常龐大, 有一些復(fù)雜的新特性需要以“公測”的形式放出牧牢, 所以設(shè)計者重新啟用了副版本號看锉, 將它用于標(biāo)識“技術(shù)預(yù)覽版”功能特性的支持姿锭。 如果Class文件中使用了該版本JDK尚未列入正式特性清單中的預(yù)覽功能, 則必須把次版本號標(biāo)識為65535伯铣, 以便Java虛擬機(jī)在加載類文件時能夠區(qū)分出來艾凯。
常量池
constant_pool_count
是一個u2
類型的數(shù)據(jù),代表了常量池容量計數(shù)器懂傀, 這個容量計數(shù)是從1而不是0開始的趾诗。如上面例子中的00 12即十進(jìn)制18,那么常量池的索引范圍為1~17蹬蚁。在Class文件格式規(guī)范制定之時恃泪, 設(shè)計者將第0項常量空出來是有特殊考慮的, 這樣做的目的在于犀斋, 如果后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下
需要表達(dá)“不引用任何一個常量池項目”的含義贝乎,可以把索引值設(shè)置為0來表示。
常量池中主要存放兩大類常量: 字面量( Literal) 和符號引用( Symbolic References) 叽粹。
- 字面量比較接近于Java語言層面的常量概念览效, 如文本字符串、 被聲明為final的常量值等虫几。
- 而符號引用則屬于編譯原理方面的概念锤灿, 主要包括下面幾類常量:
- 被模塊導(dǎo)出或者開放的包( Package)
- 類和接口的全限定名( Fully Qualified Name)
- 字段的名稱和描述符( Descriptor)
- 方法的名稱和描述符
- 方法句柄和方法類型( Method Handle、 Method Type辆脸、 Invoke Dynamic)
- 動態(tài)調(diào)用點(diǎn)和動態(tài)常量( Dynamically-Computed Call Site但校、 Dynamically-Computed Constant)
以下是JDK 8支持的常量類型。
常量類型 | 標(biāo)志 | 描述 |
---|---|---|
CONSTANT_Class |
7 | 類或接口的符號引用 |
CONSTANT_Fieldref |
9 | 字段的符號引用 |
CONSTANT_Methodref |
10 | 類中方法的符號引用 |
CONSTANT_InterfaceMethodref |
11 | 接口中方法的符號引用 |
CONSTANT_String |
8 | 字符串類型字面量 |
CONSTANT_Integer |
3 | 整型字面量 |
CONSTANT_Float |
4 | 浮點(diǎn)型字面量 |
CONSTANT_Long |
5 | 長整形字面量 |
CONSTANT_Double |
6 | 雙精度浮點(diǎn)型字面量 |
CONSTANT_NameAndType |
12 | 字段或方法的部分符號引用 |
CONSTANT_Utf8 |
1 | UTF8編碼的字符串 |
CONSTANT_MethodHandle |
15 | 方法句柄 |
CONSTANT_MethodType |
16 | 方法類型 |
CONSTANT_InvokeDynamic |
18 | 動態(tài)計算常量 |
這些常量類型彼此都沒有什么關(guān)系啡氢,它們是不同的結(jié)構(gòu)體状囱,對應(yīng)著字節(jié)碼中一段字節(jié)流,這段字節(jié)流的長度因常量類型而異倘是。它們的共性是都由1字節(jié)的tag
開頭亭枷。
00000000 ca fe ba be 00 00 00 34 00 12 0a 00 04 00 0e 09 |.......4........|
00000010 00 03 00 0f 07 00 10 07 00 11 01 00 01 61 01 00 |.............a..|
00000020 01 49 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |.I...<init>...()|
上面例子中常量池的第1項的tag
為0a,是一個Methodref
搀崭,它的結(jié)構(gòu)如下叨粘,一共占5字節(jié)0a 00 04 00 0e。
-
class_index
表示常量池中一個Class
類型的下標(biāo)门坷,其值為00 04即第4項宣鄙。 -
name_and_type_index
表示常量池中一個NameAndType
類型的下標(biāo)袍镀,其值為00 0e即第14項默蚌。
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
緊接著第2項的tag
為09,是一個Fieldref
苇羡,它的結(jié)構(gòu)與Methodref
一樣绸吸,占用5字節(jié)09 00 03 00 0f。
-
class_index
值為00 03即第3項。 -
name_and_type_index
值為00 0f即第15項锦茁。
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
第3項的tag
為07攘轩,是一個Class
,它的結(jié)構(gòu)如下码俩,一共占用3字節(jié)07 00 10度帮。
-
name_index
表示常量池中一個Utf8
類型的下標(biāo),其值為00 10即第16項稿存。
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
第4項的tag
為07笨篷,同上,指向第17項瓣履。
第5項的tag
為01率翅,是一個Utf8
,它的結(jié)構(gòu)如下袖迎,長度是變化的冕臭。
-
length
是字符串的字節(jié)數(shù),其值為00 01即1燕锥。 -
bytes
是一個字節(jié)數(shù)組辜贵,其值為0x61即97,是UTF-8編碼表示的字符串a
(英文字符的UTF-8編碼與ASCII碼相同)归形∧罹保看到這里,應(yīng)該可以猜到连霉,這個是代碼中字段a
的名稱了榴芳。
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
第6項的tag
為01
,同上跺撼,是一個字符串I
窟感。
第7項的tag
為01
,同上歉井,是一個字符串<init>
柿祈。
第8項的tag
為01
,同上哩至,是一個字符串()V
躏嚎。
第9項的tag
為01
,同上菩貌,是一個字符串Code
卢佣。
第10項也是字符串LineNumberTable
。
第11項也是字符串inc
箭阶。
第12項也是字符串SourceFile
虚茶。
第13項也是字符串ConstantTest.java
戈鲁。
00000070 61 0c 00 07 00 08 0c 00 05 00 06 01 00 10 65 78 |a.............ex|
00000080 33 2f 43 6f 6e 73 74 61 6e 74 54 65 73 74 01 00 |3/ConstantTest..|
00000090 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 |.java/lang/Objec|
000000a0 74 00 21 00 03 00 04 00 00 00 01 00 00 00 05 00 |t.!.............|
第14項tag
為0c
,是一個NameAndType
嘹叫,其結(jié)構(gòu)如下婆殿,一共占用5字節(jié)0c 00 07 00 08。
-
name_index
是常量池中一個Utf8_info
的下標(biāo)罩扇,表示方法名稱婆芦,其值為00 07即7,即<init>
喂饥。 -
descriptor_index
也是一個Utf8_info
的下標(biāo)寞缝,表示一個字段描述符(變量類型)或方法描述符(方法參數(shù)和返回值類型),其值為00 08即8仰泻,即()V
表示方法沒有參數(shù)荆陆,返回void類型。
這一項合起來就是<init>:()V
集侯,描述的是該類中的一個方法被啼,方法名稱為<init>
,沒有參數(shù)棠枉,沒有返回值浓体。
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
第15項tag
為0c
,同上辈讶,合起來是a:I
命浴,描述的是一個字段,字段名稱為a
贱除,類型為int
生闲。
第16項tag
為01
,是一個Utf8
月幌,長度為16碍讯,內(nèi)容為65 78 33 2f 43 6f 6e 73 74 61 6e 74 54 65 73 74,即ex3/ConstantTest
扯躺。
第17項tag
為01
捉兴,是一個Utf8
,長度為16录语,內(nèi)容為6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74倍啥,即java/lang/Object
。
access_flags
訪問標(biāo)志 | 值 | 描述 |
---|---|---|
ACC_PUBLIC |
0x0001 | public |
ACC_FINAL |
0x0010 | final |
ACC_SUPER |
0x0020 | JDK 1.0.2之后所有類都必須帶上這個標(biāo)志 |
ACC_INTERFACE |
0x0200 | interface |
ACC_ABSTRACT |
0x0400 | abstract |
ACC_SYNTHETIC |
0x1000 | 標(biāo)識這個類不是由用戶代碼產(chǎn)生的 |
ACC_ANNOTATION |
0x2000 | annotation |
ACC_ENUM |
0x4000 | enum |
在常量池結(jié)束之后澎埠,緊接著的2個字節(jié)代表訪問標(biāo)志(access_flags)虽缕,這個標(biāo)志用于識別一些類或 者接口層次的訪問信息,包括:這個Class是類還是接口失暂;是否定義為public類型彼宠;是否定義為abstract 類型鳄虱;如果是類的話弟塞,是否被聲明為final凭峡;等等。
000000a0 74 00 21 00 03 00 04 00 00 00 01 00 00 00 05 00 |t.!.............|
上面例子中的access_flags
值為00 21即决记,ACC_PUBLIC|ACC_SUPER
摧冀,表示這是一個public類。
類索引系宫、父類索引與接口索引集合
類索引(this_class)和父類索引(super_class)都是一個u2類型的數(shù)據(jù)索昂,而接口索引集合 (interfaces)是一組u2類型的數(shù)據(jù)的集合,Class文件中由這三項數(shù)據(jù)來確定該類型的繼承關(guān)系扩借。類索 引用于確定這個類的全限定名椒惨,父類索引用于確定這個類的父類的全限定名。
由于Java語言不允許多 重繼承潮罪,所以父類索引只有一個康谆,除了java.lang.Object
之外,所有的Java類都有父類嫉到,因此除了 java.lang.Object外沃暗,所有Java類的父類索引都不為0。
接口索引集合就用來描述這個類實(shí)現(xiàn)了哪些接口何恶,這些被實(shí)現(xiàn)的接口將按implements
關(guān)鍵字(如果這個Class文件表示的是一個接口孽锥,則應(yīng)當(dāng)是extends
關(guān)鍵字)后的接口順序從左到右排列在接口索引集合中。
他們都是u2類型的數(shù)據(jù)细层,是指向常量池中一個Class
類型的索引惜辑,而Class
類型的索引則包含指向一個Utf8
的索引。
this_class
是u2類型的數(shù)據(jù)疫赎,是指向常量池中一個Class
類型的索引韵丑。上面例子中的值為00 03,即常量池中第3項虚缎,指向第16項撵彻,內(nèi)容為ex3/ConstantTest
。
super_class
同上实牡,值為00 04陌僵,即第4項,指向第17項创坞,內(nèi)容為java/lang/Object
碗短。
接口索引集合首先包含一個u2類型的數(shù)據(jù),表示接口數(shù)量题涨,然后向后尋找接口數(shù)量個指向Class
類型的索引偎谁。例子中值為00 00总滩,表示沒有實(shí)現(xiàn)接口。
字段表
000000a0 74 00 21 00 03 00 04 00 00 00 01 00 00 00 05 00 |t.!.............|
000000b0 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 |................|
fields_count
是u2數(shù)據(jù)巡雨,表示類擁有多少個字段闰渔。在上面例子中值為00 01,表明有1個字段铐望。
field_info
結(jié)構(gòu)用于描述接口或者類中聲明的變量冈涧。Java語言中的“字段”(Field)包括類級變量以及實(shí)例級變量,但不包括在方法內(nèi)部聲明的局部變量正蛙。字段可以包括的修飾符有字段的作用域(public督弓、private、protected修飾 符)乒验、是實(shí)例變量還是類變量(static修飾符)愚隧、可變性(final)、并發(fā)可見性(volatile修飾符锻全,是否 強(qiáng)制從主內(nèi)存讀寫)狂塘、可否被序列化(transient修飾符)、字段數(shù)據(jù)類型(基本類型虱痕、對象睹耐、數(shù)組)、 字段名稱部翘。上述這些信息中硝训,各個修飾符都是布爾值,要么有某個修飾符新思,要么沒有窖梁,很適合使用標(biāo)志位來表示。而字段叫做什么名字夹囚、字段被定義為什么數(shù)據(jù)類型纵刘,這些都是無法固定的,只能引用常量池中的常量來描述荸哟。
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
access_flags
是方法的訪問修飾符假哎,與類的access_flags
一樣,都是u2
類型鞍历。在上面例子中值為00 00舵抹,表明沒有加修飾符。
標(biāo)志 | 值 | 描述 |
---|---|---|
ACC_PUBLIC |
0x0001 | public |
ACC_PRIVATE |
0x0002 | private |
ACC_PROTECTED |
0x0004 | protected |
ACC_STATIC |
0x0008 | static |
ACC_FINAL |
0x0010 | final |
ACC_VOLATILE |
0x0040 | volatile |
ACC_TRANSIENT |
0x0080 | transient |
ACC_SYNTHETIC |
0x1000 | 是否由編譯器自動產(chǎn)生 |
ACC_ENUM |
0x4000 | enum |
name_index
是名稱索引劣砍,指向常量池中一個Utf8
惧蛹,例子中值為00 05即第5項,是字符串a
,表明這個字段名稱為a
香嗓。
descriptor_index
是描述符索引迅腔,指向常量池中一個Utf8
,例子中值為00 06靠娱,是字符串I
沧烈,表明這個字段類型為int
。
attributes_count
是屬性數(shù)量饱岸,值為00 00掺出,表示沒有屬性徽千。
方法表
000000b0 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 |................|
000000c0 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 |...........*....|
方法表結(jié)構(gòu)與字段表幾乎一樣苫费。
methods_count
表示方法數(shù)量,在上面例子中值為00 02双抽,即有兩個方法百框,后面有兩個method_info
項。
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
方法的access_flags
與字段有所不同牍汹。
標(biāo)志 | 值 | 描述 |
---|---|---|
ACC_PUBLIC |
0x0001 | public |
ACC_PRIVATE |
0x0002 | private |
ACC_PROTECTED |
0x0004 | protected |
ACC_STATIC |
0x0008 | static |
ACC_FINAL |
0x0010 | final |
ACC_SYNCHRONIZED |
0x0020 | synchronized |
ACC_BRIDGE |
0x0040 | 由編譯器產(chǎn)生的橋接方法 |
ACC_VARARGS |
0x0080 | 方法接受不定參數(shù) |
ACC_NATIVE |
0x0100 | native |
ACC_ABSTRACT |
0x0400 | abstract |
ACC_STRICT |
0x0800 | strictfp |
ACC_SYNTHETIC |
0x1000 | 方法由編譯器生成 |
方法1
access_flags
值為00 01铐维,表示是public方法。
name_index
值為00 07慎菲,指向字符串<init>
嫁蛇,這是方法名稱。
descriptor_index
值為00 08露该,指向字符串()V
睬棚,表示方法沒有參數(shù),返回類型為void解幼。
attributes_count
值為00 01抑党,表示有1個attribute_info
。
attribute_info
結(jié)構(gòu)如下撵摆,長度是變化的底靠。
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
attribute_name_index
值為00 09
,指向字符串Code
特铝。
attribute_length
值為00 00 00 1d即29暑中,表明后面的info
字段長度為29字節(jié)。
000000c0 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 |...........*....|
000000d0 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 00 03 |................|
info
是Code屬性鲫剿,其結(jié)構(gòu)定義如下鳄逾。
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
max_stack
值為00 01,表明操作數(shù)棧的深度為1牵素。
max_locals
值為00 01严衬,表明局部變量數(shù)量為1。
code_length
值為00 00 00 05笆呆,表明代碼長度為5请琳。
code
值為2a b7 00 01 b1粱挡,代表的指令分別為
- 2a
aload_0
- b7 00 01``invokespecial #00.#01`
- b1
return
exception_table_length
值為00 00,代表異常表為空俄精,即不拋出異常询筏。
attrbutes_count
值為00 01,表明屬性表中有一個屬性竖慧。
attribute_name_index
值為00 0a嫌套,指向字符串LineNumberTable
。LineNumberTable屬性描述java源碼和字節(jié)碼的對應(yīng)關(guān)系圾旨,它并不是運(yùn)行時必備的屬性踱讨,但默認(rèn)會生成到class文件中】车模可以用javac -g:none
來禁用LineNumberTable痹筛,這樣程序在拋出異常的時候?qū)⒉粫@示出錯的行號,并且在調(diào)試程序的時候廓鞠,也無法按照源碼行來設(shè)置斷點(diǎn)帚稠。
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}
attribute_length
值為00 00 00 06,表明后面還有6字節(jié)床佳。
line_number_table_length
值為00 01滋早,表明line_number_table
中有一項。
這一項的start_pc
值為00 00砌们,line_number
值為00 03杆麸,表明java源代碼中行號為3的位置對應(yīng)著class中該方法的起始位置。
方法2
000000e0 00 01 00 0b 00 08 00 01 00 09 00 00 00 27 00 03 |.............'..|
000000f0 00 01 00 00 00 0b 2a 59 b4 00 02 04 60 b5 00 02 |......*Y....`...|
00000100 b1 00 00 00 01 00 0a 00 00 00 0a 00 02 00 00 00 |................|
00000110 06 00 0a 00 07 00 01 00 0c 00 00 00 02 00 0d |...............|
0000011f
access_flags
為00 01怨绣,表明是public方法角溃。
name_index
值為00 0b,指向字符串inc
篮撑,這是方法名稱减细。
descriptor_index
值為00 08,指向字符串()V
赢笨,表示方法沒有參數(shù)未蝌,返回類型為void。
attributes_count
值為00 01茧妒,表示有1個attribute_info
萧吠。
attribute_name_index
值為00 09,指向字符串Code
桐筏,因此這個屬性也是Code屬性纸型。
attribute_length
值為00 00 00 27即39,表明后面的info
字段長度為39字節(jié)。
max_stack
值為00 03狰腌,表明操作數(shù)棧的深度為3除破。
max_locals
值為00 01,表明局部變量數(shù)量為1琼腔。
code_length
值為00 00 00 0b瑰枫,表明代碼長度為11。
code
值為2a 59 b4 00 02 04 60 b5 00 02 b1丹莲,代表的指令分別為光坝,顯然這與inc
方法相符。
- 2a
aload_0
- 59
dup
- b4 00 02
getfield #00.#02
- 02
iconst_1
- 60
iadd
- b5 00 02
putfield #00.#02
- b1
return
exception_table_length
值為00 00甥材,代表異常表為空盯另,即不拋出異常。
attrbutes_count
值為00 01擂达,表明屬性表中有一個屬性土铺。
attribute_name_index
值為00 0a胶滋,指向字符串LineNumberTable
板鬓,表明是一個LineNumberTable屬性。
attribute_length
值為00 00 00 0a究恤,表明后面還有10字節(jié)俭令。
line_number_table_length
值為00 02,表明line_number_table
中有2項部宿。
第1項的start_pc
值為00 00抄腔,line_number
值為00 06,表明java源代碼中行號為6的位置對應(yīng)著code的起始位置理张。
第2項的start_pc
值為00 0a赫蛇,line_number
值為00 07,表明java源代碼中行號為7的位置對應(yīng)著code第10字節(jié)的位置即return
雾叭。
屬性表
00000110 06 00 0a 00 07 00 01 00 0c 00 00 00 02 00 0d |...............|
0000011f
attributes_count
值為00 01
悟耘,表明有一個屬性。
attribute_name_index
值為00 0c织狐,指向字符串SourceFile
暂幼,表明是一個SourceFile屬性。其結(jié)構(gòu)如下移迫,占用8字節(jié)旺嬉。
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
attribute_length
值為00 00 00 02,表明attribute_length
之后的屬性內(nèi)容一共占2字節(jié)厨埋。
sourcefile_index
值為00 0d邪媳,指向常量池中第13向,即字符串ConstantTest.java
。
至此雨效,字節(jié)碼文件中的每一個字節(jié)都解析完了套菜!
總結(jié)
- 本文對class文件進(jìn)行了逐字節(jié)分析,做了
javap
類似的工作设易,也得到了相符的結(jié)果逗柴。 - Cafe babe很有趣,Java的設(shè)計者們太會了顿肺!
- 各種定長結(jié)構(gòu)體戏溺、變長結(jié)構(gòu)體設(shè)計得非常巧妙,空間很緊湊屠尊,一點(diǎn)都沒浪費(fèi)旷祸。
- 常量池非常重要,字節(jié)碼指令中包含對常量池的引用讼昆,常量池中的數(shù)據(jù)也有遞歸引用托享。
- 方法中的代碼由一段Code屬性描述,它就是一個字節(jié)數(shù)組浸赫。
- 構(gòu)造方法的名稱為
<init>
闰围。
了解了class文件結(jié)構(gòu)之后,就要開始探索類加載機(jī)制了既峡。