一步步翻譯class,探究class文件結(jié)構(gòu)跌宛。
自己去一點(diǎn)點(diǎn)把class文件格式化隆圆,是一件非常有意思的事情。
1. 準(zhǔn)備
用來測試的java類:
public class Test {
public static final String s = "good";
int i;
public void set(int i) {
this.i = i;
}
}
這樣一個(gè)簡單的java類文件,通過javac工具編譯成class后都包含什么東西呢岁经?
用記事本打開Test.class,得到如下:
cafe babe 0000 0034 0018 0a00 0400 1309
0003 0014 0700 1507 0016 0100 0173 0100
124c 6a61 7661 2f6c 616e 672f 5374 7269
6e67 3b01 000d 436f 6e73 7461 6e74 5661
6c75 6508 0017 0100 0169 0100 0149 0100
063c 696e 6974 3e01 0003 2829 5601 0004
436f 6465 0100 0f4c 696e 654e 756d 6265
7254 6162 6c65 0100 0373 6574 0100 0428
4929 5601 000a 536f 7572 6365 4669 6c65
0100 0954 6573 742e 6a61 7661 0c00 0b00
0c0c 0009 000a 0100 0454 6573 7401 0010
6a61 7661 2f6c 616e 672f 4f62 6a65 6374
0100 0467 6f6f 6400 2100 0300 0400 0000
0200 1900 0500 0600 0100 0700 0000 0200
0800 0000 0900 0a00 0000 0200 0100 0b00
0c00 0100 0d00 0000 1d00 0100 0100 0000
052a b700 01b1 0000 0001 000e 0000 0006
0001 0000 0001 0001 000f 0010 0001 000d
0000 0022 0002 0002 0000 0006 2a1b b500
02b1 0000 0001 000e 0000 000a 0002 0000
0007 0005 0008 0001 0011 0000 0002 0012
最早的時(shí)候我看見這一堆涨椒,絕對要驚呼亂碼了忧侧。接下來就要把這些16進(jìn)制的字節(jié)一步步翻譯成我們看得懂的東西,從而去探究class的文件結(jié)構(gòu)沪斟。
假如還看過一點(diǎn)jvm相關(guān)知識史翘,翻譯著翻譯著會(huì)心一笑,原來如此注整。
1.1 javap工具
因?yàn)橐骄?6進(jìn)制字節(jié)形式的class文件的具體結(jié)構(gòu),肯定是自己去把16進(jìn)制字節(jié)一點(diǎn)點(diǎn)翻譯過來會(huì)比較印象深刻森枪。
使用javap工具呢漓滔,可以把class文件直接翻譯成我們看得懂的英文的形式。這樣我們也可以用來作為參照耘纱,看自己是否翻譯對了。還能省去把16進(jìn)制字節(jié)轉(zhuǎn)換成英文utf-8字符串的步驟。
javap -verbose Test.class
Classfile /Users/lanzry/Documents/java/test/Test.class
Last modified 2018年3月17日; size 336 bytes
MD5 checksum b4674caa94520a1eeb8363c79b349ef2
Compiled from "Test.java"
public class Test
minor version: 0
major version: 53
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #3 // Test
super_class: #4 // java/lang/Object
interfaces: 0, fields: 2, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #4.#19 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#20 // Test.i:I
#3 = Class #21 // Test
#4 = Class #22 // java/lang/Object
#5 = Utf8 s
#6 = Utf8 Ljava/lang/String;
#7 = Utf8 ConstantValue
#8 = String #23 // good
#9 = Utf8 i
#10 = Utf8 I
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 set
#16 = Utf8 (I)V
#17 = Utf8 SourceFile
#18 = Utf8 Test.java
#19 = NameAndType #11:#12 // "<init>":()V
#20 = NameAndType #9:#10 // i:I
#21 = Utf8 Test
#22 = Utf8 java/lang/Object
#23 = Utf8 good
{
public static final java.lang.String s;
descriptor: Ljava/lang/String;
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String good
int i;
descriptor: I
flags: (0x0000)
public Test();
descriptor: ()V
flags: (0x0001) 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 1: 0
public void set(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field i:I
5: return
LineNumberTable:
line 7: 0
line 8: 5
}
SourceFile: "Test.java"
2. 手動(dòng)格式化
這樣的文件权薯,就像沒有標(biāo)點(diǎn)符號的文言文甩鳄,令人生畏铭拧。我要把它格式化瘟芝。
# 魔數(shù)
cafe babe
# java版本號(換算成十進(jìn)制=52,即java8)
0000 0034
# 常量池大蟹质(換算=24史飞,常量池第0項(xiàng)空著,所以共23個(gè))
0018
# 常量池
# 16進(jìn)制是4位纹笼,8位是一個(gè)字節(jié)羽峰,所以兩個(gè)數(shù)字是一字節(jié)
# 第一個(gè)字節(jié)是常量的類型,后面跟著該類型常量的屬性或者值
0a 0004 0013
09 0003 0014
07 0015
07 0016
01 0001 73
01 0012 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b
01 000d 436f 6e73 7461 6e74 5661 6c75 65
08 0017
01 0001 69
01 0001 49
01 0006 3c69 6e69 743e
01 0003 2829 56
01 0004 436f 6465
01 000f 4c 696e 654e 756d 6265 7254 6162 6c65
01 0003 73 6574
01 0004 2849 2956
01 000a 536f 7572 6365 4669 6c65
01 0009 5465 7374 2e6a 6176 61
0c 000b 000c
0c 0009 000a
01 0004 5465 7374
01 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374
01 0004 676f 6f64
# 當(dāng)前類的訪問修飾符
0021
# 當(dāng)前類的索引值纱,指向常量池第3項(xiàng)
0003
# 父類索引
0004
# 接口個(gè)數(shù)
0000
# 字段表(字段數(shù)量開頭鳞贷,兩個(gè)字段)
0002
0019 0005 0006 0001 0007 00000002 0008
0000 0009 000a 0000
# 方法表
0002
# 方法1
0001 000b 000c 0001 000d 0000001d 0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0e00 0000 0600 0100 0000 01
# 方法2
0001 000f 0010 0001 000d 00000022 0002 0002 0000 0006 2a1b b500
02b1 0000 0001 000e 0000 000a 0002 0000 0007 0005 0008
# 屬性表
0001
0011 00000002 0012
大功告成,把一個(gè)class文件虐唠,按自己知道的去格式化出來了搀愧,這樣就清爽很多了。
自己把class文件整理一遍疆偿,真是能非常加深印象霸凵浮!
你看杆故,一開始是魔數(shù)迅箩,版本號,常量池处铛,類的訪問修飾符饲趋,類索引,父類索引撤蟆,接口索引奕塑,字段表,方法表家肯,屬性表龄砰,over!就這些讨衣,非常規(guī)整换棚!
當(dāng)然會(huì)看這篇文章的小伙伴,證明你不太明白如何這樣整理值依。參照我接下來的知識就OK了役首!
接下來张足,就要看看是如何格式化出來的了!Q价说!
3. 相關(guān)的java虛擬機(jī)規(guī)范
下面會(huì)給出參考Java9的虛擬機(jī)規(guī)范的相關(guān)知識點(diǎn)辆亏,會(huì)附上原英文鏈接。
3.1 class文件結(jié)構(gòu)
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];
}
左邊一列是u1鳖目、u2扮叨、u3、u4和_info领迈。
u1彻磁、u2是什么呢碍沐?其實(shí)是無符號數(shù),后面數(shù)字表示幾個(gè)字節(jié)衷蜓。比如u1表示一個(gè)字節(jié)的無符號數(shù)累提。比如上述第一個(gè)u4 magic,意思是class文件結(jié)構(gòu)第一個(gè)是一個(gè)由4個(gè)字節(jié)表示的叫做“魔數(shù)”的東西磁浇。然后接下來兩個(gè)字節(jié)的minor version斋陪,2個(gè)字節(jié)的major version...
_info表示這是一個(gè)表。class文件中有很多表置吓,每個(gè)表有不同的結(jié)構(gòu)无虚。上述我們能看見的就有常量池表、字段表衍锚、方法表等等友题。表其實(shí)就是一個(gè)相同數(shù)據(jù)的集合,和列表的意思一樣戴质,每種表的結(jié)構(gòu)是不一樣的咆爽,具體碰到的時(shí)候再去研究。
接下來置森,就按這張圖的順序斗埂,將以上16進(jìn)制的數(shù)據(jù)一一解釋成我們能看的懂的方式。
3.2 常量池
Constant Type | Value | class file | Java SE |
---|---|---|---|
CONSTANT_Class | 7 | 45.3 | 1.0.2 |
CONSTANT_Fieldref | 9 | 45.3 | 1.0.2 |
CONSTANT_Methodref | 10 | 45.3 | 1.0.2 |
CONSTANT_InterfaceMethodref | 11 | 45.3 | 1.0.2 |
CONSTANT_String | 8 | 45.3 | 1.0.2 |
CONSTANT_Integer | 3 | 45.3 | 1.0.2 |
CONSTANT_Float | 4 | 45.3 | 1.0.2 |
CONSTANT_Long | 5 | 45.3 | 1.0.2 |
CONSTANT_Double | 6 | 45.3 | 1.0.2 |
CONSTANT_NameAndType | 12 | 45.3 | 1.0.2 |
CONSTANT_Utf8 | 1 | 45.3 | 1.0.2 |
CONSTANT_MethodHandle | 15 | 51.0 | 7 |
CONSTANT_MethodType | 16 | 51.0 | 7 |
CONSTANT_InvokeDynamic | 18 | 51.0 | 7 |
CONSTANT_Module | 19 | 53.0 | 9 |
CONSTANT_Package | 20 | 53.0 | 9 |
常量池的類型一共以上這些凫海,他們的結(jié)構(gòu)也各不相同呛凶,全部列出來太多了。大家有興趣可以自己去官方文檔了解行贪,走各個(gè)小節(jié)的英文文檔入口漾稀。
這里把我們翻譯的部分的幾個(gè)類型列一下,一共是0a建瘫、09崭捍、07、01啰脚、08殷蛇、0c這幾個(gè)。
3.2.1 The CONSTANT_Fieldref_info, CONSTANT_Methodref_info
16進(jìn)制 | 10進(jìn)制 | 對應(yīng) | 意思 |
---|---|---|---|
0a | 10 | CONSTANT_Methodref | 方法符號引用 |
09 | 9 | CONSTANT_Fieldref_info | 字段符號引用 |
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
0a橄浓、09分別是方法和字段粒梦,二者的結(jié)構(gòu)一模一樣,所以一起寫荸实。
例如方法符號引用的原文:0a 0004 0013
按上述結(jié)構(gòu)對應(yīng)起來匀们,0a表示常量類型(tag),0004表示該方法所在類的索引是常量池第4個(gè)准给,0013表示該方法的NameAndType屬性的索引位置是常量池第19個(gè)(16進(jìn)制的13)泄朴。
3.2.2 CONSTANT_Class
16進(jìn)制 | 10進(jìn)制 | 對應(yīng) | 意思 |
---|---|---|---|
07 | 7 | CONSTANT_Class | 類符號引用 |
The CONSTANT_Class_info Structure
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
例:07 0015
類型是7重抖,表示類符號引用,類名描述符是常量池21(16進(jìn)制的15)項(xiàng)祖灰。
再看常量池21項(xiàng)是這樣的:01 0004 5465 7374
具體什么意思呢仇哆?接下去
3.2.3 CONSTANT_Utf8_info Structure
16進(jìn)制 | 10進(jìn)制 | 對應(yīng) | 意思 |
---|---|---|---|
01 | 7 | CONSTANT_Utf8_info | utf-8字符串 |
The CONSTANT_Utf8_info Structure
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
前一小節(jié)例子:01 0004 5465 7374
表示3個(gè)字節(jié)長的utf-8類型數(shù)據(jù),那么5465 7374這三個(gè)字節(jié)是什么意思夫植?
[16進(jìn)制到文本字符串的轉(zhuǎn)換讹剔,在線實(shí)時(shí)轉(zhuǎn)換]
很明顯了,就是我們的類Test.java详民,類名就是Test延欠。
3.2.4 The CONSTANT_String_info Structure
16進(jìn)制 | 10進(jìn)制 | 對應(yīng) | 意思 |
---|---|---|---|
08 | 7 | CONSTANT_String_info | String類型常量 |
CONSTANT_String_info Structure
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
例:08 0017
3.2.6 The CONSTANT_NameAndType_info Structure
16進(jìn)制 | 10進(jìn)制 | 對應(yīng) | 意思 |
---|---|---|---|
0c | 12 | CONSTANT_NameAndType_info | NameAndType類型 |
CONSTANT_NameAndType_info Structure
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
例:0c 000b 000c
類型是NameAndType,名稱索引在第11項(xiàng)饿凛,描述索引是第12項(xiàng)狞玛。
常量池就介紹到這里,其他的常量池常量類型涧窒,大家就可以遇到的時(shí)候去官方文檔The Constant Pool中查找了心肪。
3.3 字段表
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
access_flags:訪問修飾符
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; usable only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; never directly assigned to after object construction (JLS §17.5). |
ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached. |
ACC_TRANSIENT | 0x0080 | Declared transient; not written or read by a persistent object manager. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
ACC_ENUM | 0x4000 | Declared as an element of an enum. |
name_index、descriptor_index纠吴,依舊是常量池的索引硬鞍。
attribute_info,是一個(gè)屬性表戴已。依舊會(huì)是屬性數(shù)量固该,然后屬性的列表的形式。后文再介紹糖儡。
3.4 方法表
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
和字段表差別不大伐坏。字段和方法的訪問修飾符還是有所差別的,下面列出來:
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 | Declared public ; may be accessed from outside its package. |
ACC_PRIVATE |
0x0002 | Declared private ; accessible only within the defining class. |
ACC_PROTECTED |
0x0004 | Declared protected ; may be accessed within subclasses. |
ACC_STATIC |
0x0008 | Declared static . |
ACC_FINAL |
0x0010 | Declared final ; must not be overridden (§5.4.5). |
ACC_SYNCHRONIZED |
0x0020 | Declared synchronized ; invocation is wrapped by a monitor use. |
ACC_BRIDGE |
0x0040 | A bridge method, generated by the compiler. |
ACC_VARARGS |
0x0080 | Declared with variable number of arguments. |
ACC_NATIVE |
0x0100 | Declared native ; implemented in a language other than the Java programming language. |
ACC_ABSTRACT |
0x0400 | Declared abstract ; no implementation is provided. |
ACC_STRICT |
0x0800 | Declared strictfp ; floating-point mode is FP-strict. |
ACC_SYNTHETIC |
0x1000 | Declared synthetic; not present in the source code. |
還是舉個(gè)例子握联,做一次翻譯:
# 方法1
0001 000b 000c 0001 000d 0000001d 0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0e00 0000 0600 0100 0000 01
0001:ACC_PUBLIC
000b:方法名 = 常量池#11 = Utf8 = <init>(也就是默認(rèn)的構(gòu)造方法)
000c:方法描述 = 常量池#12 = Utf8 = ()V
括號內(nèi)空桦沉,表示沒有參數(shù);V表示返回值是void拴疤。這部分的文檔見Method Descriptors永部。
0001:表示屬性表只有一個(gè)屬性。
屬性表
000d
0000001d
0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0e00 0000 0600 0100 0000 01
000d:#13 = Utf8 = Code呐矾,表示是一個(gè)Code屬性
The Code Attribute
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];
}
看這個(gè)就很有意思了,這里面像發(fā)現(xiàn)寶藏一樣懦砂。比如max_stack蜒犯、max_locals是不是就是操作數(shù)棧组橄、局部變量表的最大大小呢?之類罚随,很有意思玉工。
我們來翻譯一遍:
# Code
000d
# 長度29
0000001d
# max_stack=1
0001
# max_locals=1
0001
# code_length=5
00000005
2ab7 0001 b1
# 沒有異常,所以異常表是0
0000
# 后面又接著1個(gè)屬性表
0001
# 14 = Utf8 = LineNumberTable屬性
000e
# LineNumberTable長度
00000006
# start_pc這個(gè)就很容易懂了
0001
# start_pc
0000
# lineNumber
0001
簡單介紹一下The LineNumberTable Attribute淘菩,就是字節(jié)碼對應(yīng)源代碼的行號遵班,這樣調(diào)試的時(shí)候可以根據(jù)這個(gè)找到當(dāng)前執(zhí)行到了哪一行。
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];
}
3.5 屬性表
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
把最后多余出來的屬性表作為例子:
# 屬性表
0001
0011 00000002 0012
該屬性名字是常量池第17項(xiàng)(16進(jìn)制的11)潮改,01 000a 536f 7572 6365 4669 6c65狭郑,01類型是utf-8字符串,轉(zhuǎn)換一下:SourceFile
屬性表里面又分很多屬性類別汇在,定位到屬性名稱后翰萨,再去官方查找對應(yīng)文檔即可。
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
值是長度是2個(gè)字節(jié)糕殉。最后一項(xiàng)sourcefile_index索引亩鬼,0012,對應(yīng)常量池18項(xiàng):01 0009 5465 7374 2e6a 6176 61 阿蝶,轉(zhuǎn)換一下:Test.java
意思就是記錄源文件名字是Test.java雳锋。
這里列出所有屬性的類型:
Table 4.7-A. Predefined class
file attributes (by section)
Attribute | Section |
class file |
Java SE |
---|---|---|---|
ConstantValue |
§4.7.2 | 45.3 | 1.0.2 |
Code |
§4.7.3 | 45.3 | 1.0.2 |
StackMapTable |
§4.7.4 | 50.0 | 6 |
Exceptions |
§4.7.5 | 45.3 | 1.0.2 |
InnerClasses |
§4.7.6 | 45.3 | 1.1 |
EnclosingMethod |
§4.7.7 | 49.0 | 5.0 |
Synthetic |
§4.7.8 | 45.3 | 1.1 |
Signature |
§4.7.9 | 49.0 | 5.0 |
SourceFile |
§4.7.10 | 45.3 | 1.0.2 |
SourceDebugExtension |
§4.7.11 | 49.0 | 5.0 |
LineNumberTable |
§4.7.12 | 45.3 | 1.0.2 |
LocalVariableTable |
§4.7.13 | 45.3 | 1.0.2 |
LocalVariableTypeTable |
§4.7.14 | 49.0 | 5.0 |
Deprecated |
§4.7.15 | 45.3 | 1.1 |
RuntimeVisibleAnnotations |
§4.7.16 | 49.0 | 5.0 |
RuntimeInvisibleAnnotations |
§4.7.17 | 49.0 | 5.0 |
RuntimeVisibleParameterAnnotations |
§4.7.18 | 49.0 | 5.0 |
RuntimeInvisibleParameterAnnotations |
§4.7.19 | 49.0 | 5.0 |
RuntimeVisibleTypeAnnotations |
§4.7.20 | 52.0 | 8 |
RuntimeInvisibleTypeAnnotations |
§4.7.21 | 52.0 | 8 |
AnnotationDefault |
§4.7.22 | 49.0 | 5.0 |
BootstrapMethods |
§4.7.23 | 51.0 | 7 |
MethodParameters |
§4.7.24 | 52.0 | 8 |
Module |
§4.7.25 | 53.0 | 9 |
ModulePackages |
§4.7.26 | 53.0 | 9 |
ModuleMainClass |
§4.7.27 | 53.0 | 9 |
到這里就算差不多了。簡單地了解一下簡單java類的class文件結(jié)構(gòu)羡洁,就已經(jīng)足夠了魄缚。以后即使是復(fù)雜的java類,其實(shí)我們也能明白結(jié)構(gòu)是怎么樣焚廊。即使不知道冶匹,還有官方文檔The Java? Virtual Machine Specification Java SE 9 Edition啊 哈哈