簡述
Java原始碼通過編譯生成.class檔案位元組碼後再被JVM解釋轉化為目標機器程式碼,從而實現一次編寫到處,到處執行(“Write Once,Run Anywhere”)。位元組碼與平臺無關,而且並不是只有Java語言編譯為位元組碼檔案在虛擬機器上執行。
類檔案的結構
Class檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊地排列在Class檔案中。Class檔案只有兩種資料型別:無符號數和表。
無符號數屬於基本的資料型別,有u1, u2, u4, u8,分別代表1個位元組、2個位元組、4個位元組和8個位元組的無符號數
整個Class檔案就是一張表,由以下資料項構成:
型別 | 名稱 | 數量 |
---|---|---|
u4 | magic(魔數) | 1 |
u2 | minor_version(次版本號) | 1 |
u2 | major_version(主版本號) | 1 |
u2 | constant_pool_count(常量池容量) | 1 |
cp_info | constant_pool(常量池) | constant_pool_count-1 |
u2 | access_flags(訪問標誌) | 1 |
u2 | this_class(類索引) | 1 |
u2 | super_class(父類索引) | 1 |
u2 | interfaces_count(介面容量) | 1 |
u2 | interfaces(介面) | interfaces_count |
u2 | fields_count(欄位容量) | 1 |
field_info | fields(欄位) | fields_count |
u2 | methods_count(方法容量) | 1 |
mehtod_info | methods(方法) | method_count |
u2 | attributes_count(屬性容量) | 1 |
attribute | attributes(屬性) | attributes_count |
小試牛刀
寫個簡單實體類,javac編譯後,檢視其位元組碼
原始碼
public class Person {
private int age;
public int getAge(){
return age;
}
public static synchronized void work() {
System.out.println("工作");
}
public static void main(String[] args) {
}
}
複製程式碼
十六進位制Class檔案
根據上述資料項表格我們按順序拆分
魔數站每個Class檔案的頭4個位元組,其作用未確定確定這個檔案是否為一個能被虛擬機器接受的Class檔案
示例中CA FE BA BE為魔數
魔數後面緊跟著版本號
00 00——次版本號
00 34——主版本號
根據如下:
十進位制版本號 | 主版本 |
---|---|
jdk1.8 | 52 |
jdk1.7 | 51 |
jdk1.6 | 50 |
jdk1.5 | 49 |
jdk1.4 | 48 |
jdk1.3 | 47 |
jdk1.2 | 46 |
jdk1.1 | 45 |
十六進位制0034,對應十進位制52,對應jdk1.8版本
常量池可以理解為Class檔案之中的資源倉庫,它是Class檔案結構中與其他專案關聯最多的資料型別。常量池中主要存放兩大類常量:字面量和符號引用.
字面量——接近於Java中的常量概念,eg:final修飾、文字字串
符號引用——編譯原理概念:類和介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符
常量池中的每一項常量都是一個表,每種常量都有自己的結構,14種常量含義:
型別 | 標誌 | 描述 |
---|---|---|
CONSTANT_utf8_info | 1 | utf-8編碼的字串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮點型字面量 |
CONSTANT_Long_info | 5 | 長整型字面量 |
CONSTANT_Double_info | 6 | 雙精度浮點型字面量 |
CONSTANT_Class_info | 7 | 類或者介面的符號引用 |
CONSTANT_String_info | 8 | 字串型字面量 |
CONSTANT_Fieldref_info | 9 | 欄位的符號引用 |
CONSTANT_Methodref_info | 10 | 類中方法的符號引用 |
CONSTANT_InterfaceMethoderf_info | 11 | 介面中方法的符號引用 |
CONSTANT_NameAndType_info | 12 | 欄位或方法的部分符號引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法控制程式碼 |
CONSTANT_MethodType_info | 16 | 標識方法型別 |
CONSTANT_InvokeDynamic_info | 18 | 表示一個動態方法呼叫點 |
0×0029轉十進位制為41,代表常量池中有40項常量(容量計數是從1而不是0開始。第0項常量空出來是表達“不引用任何一個常量池專案”)
0A即十進位制10,對應表中CONSTANT_Methodref_info,其結構如下:
型別 | 名稱 | 描述 |
u1 | tag | 值為10 |
u2 | index | 指向宣告方法的類描述符CONSTANT_Class_info的索引項 |
u2 | index | 指向名稱及型別描述符CONSTANT_NameAndType_info的索引項 |
0×0007為常量池中第7項CONSTANT_Class_info,0×001A為第26項CONSTANT_NameAndType_info。按照《深入理解Java虛擬機器》第二版,172頁中表6-6順序解析得:
00 29 //constant_pool_count(常量池容量)
#1、0A 0007 001A //CONSTANT_Methodref_info,#7,#26
#2、09 0006 001B //CONSTANT_Fieldref_info,#6,#27
#3、09 001C 001D //CONSTANT_Fieldref_info,#28,#29
#4、08 001E //CONSTANT_String_info,#30
#5、0A 001F 0020 //CONSTANT_Methodref_info,#31,#32
#6、07 0021 //CONSTANT_Class_info,#33
#7、07 0022 //CONSTANT_Class_info,#34
#8、01 0003 61 67 65 //CONSTANT_Utf8_info,3個位元組,age
#9、01 0001 49 //CONSTANT_Utf8_info,1個位元組,I
#10、01 0006 3C 69 6E 69 74 3E //CONSTANT_Utf8_info,6個位元組,
#11、01 0003 28 29 56 //CONSTANT_Utf8_info,3個位元組,()V
#12、01 0004 43 6F 64 65 //CONSTANT_Utf8_info,4個位元組,Code
#13、01 000F 4C 69 6E 65 4E 75 6D 62 //CONSTANT_Utf8_info,15個位元組,LineNumberTable
65 72 54 61 62 6C 65
#14、01 0012 4C 6F 63 61 6C 56 61 72 //CONSTANT_Utf8_info,18個位元組,LocalVariableTable
69 61 62 6C 65 54 61 62
6C 65
#15、01 0004 74 68 69 73 //CONSTANT_Utf8_info,4個位元組,this
#16、01 0008 4C 50 65 72 73 6F 6E 3B //CONSTANT_Utf8_info,8個位元組,LPerson;
#17、01 0006 67 65 74 41 67 65 //CONSTANT_Utf8_info,6個位元組,getAge
#18、01 0003 28 29 49 //CONSTANT_Utf8_info,3個位元組,()I
#19、01 0004 77 6F 72 6B //CONSTANT_Utf8_info,4個位元組,work
#20、01 0004 6D 61 69 6E //CONSTANT_Utf8_info,4個位元組,main
#21、01 0016 28 5B 4C 6A 61 76 61 2F //CONSTANT_Utf8_info,22個位元組,([Ljava/lang/String;)V
6C 61 6E 67 2F 53 74 72
69 6E 67 3B 29 56
#22、01 0004 61 72 67 73 //CONSTANT_Utf8_info,4個位元組,args
#23、01 0013 5B 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info,19個位元組,[Ljava/lang/String;
61 6E 67 2F 53 74 72 69
6E 67 3B
#24、01 000A 53 6F 75 72 63 65 46 69 //CONSTANT_Utf8_info,10個位元組, SourceFile
6C 65
#25、01 000B 50 65 72 73 6F 6E 2E 6A //CONSTANT_Utf8_info,11個位元組, Person.java
61 76 61
#26、0C 000A 000B //CONSTANT_NameAndType_info,#10,#11
#27、0C 0008 0009 //CONSTANT_NameAndType_info,#8,#9
#28、07 0023 //CONSTANT_Class_info,#35
#29、0C 0024 0025 //CONSTANT_NameAndType_info,#36,#37
#30、01 0006 E5 B7 A5 E4 BD 9C //CONSTANT_Utf8_info,6個位元組,工作
#31、07 0026 //CONSTANT_Class_info,#38
#32、0C 0027 0028 //CONSTANT_NameAndType_info,#39,#40
#33、01 0006 50 65 72 73 6F 6E //CONSTANT_Utf8_info,6個位元組,Person
#34、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info,16個位元組,java/lang/Object
67 2F 4F 62 6A 65 63 74
#35、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info,16個位元組, java/lang/System
67 2F 53 79 73 74 65 6D
#36、01 0003 6F 75 74 //CONSTANT_Utf8_info,3個位元組,out
#37、01 0015 4C 6A 61 76 61 2F 69 6F //CONSTANT_Utf8_info,21個位元組, Ljava/io/PrintStream;
2F 50 72 69 6E 74 53 74
72 65 61 6D 3B
#38、01 0013 6A 61 76 2F 69 6F 2F 50 //CONSTANT_Utf8_info,19個位元組,java/io/PrintStream
72 69 6E 74 53 74 72 65
61 6D
#39、01 0007 70 72 69 6E 74 6C 6E //CONSTANT_Utf8_info,7個位元組,println
#40、01 0015 28 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info,21個位元組, (Ljava/lang/String;)V
61 6E 67 2F 53 74 72 69
6E 67 3B 29 56
複製程式碼
也可以java -verbose分析Class檔案位元組碼,得到結果:
Constant pool:
#1 = Methodref #7.#26 // java/lang/Object."":()V
#2 = Fieldref #6.#27 // Person.age:I
#3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #30 // 工作
#5 = Methodref #31.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = Class #33 // Person
#7 = Class #34 // java/lang/Object
#8 = Utf8 age
#9 = Utf8 I
#10 = Utf8
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 LPerson;
#17 = Utf8 getAge
#18 = Utf8 ()I
#19 = Utf8 work
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 SourceFile
#25 = Utf8 Person.java
#26 = NameAndType #10:#11 // "":()V
#27 = NameAndType #8:#9 // age:I
#28 = Class #35 // java/lang/System
#29 = NameAndType #36:#37 // out:Ljava/io/PrintStream;
#30 = Utf8 工作
#31 = Class #38 // java/io/PrintStream
#32 = NameAndType #39:#40 // println:(Ljava/lang/String;)V
#33 = Utf8 Person
#34 = Utf8 java/lang/Object
#35 = Utf8 java/lang/System
#36 = Utf8 out
#37 = Utf8 Ljava/io/PrintStream;
#38 = Utf8 java/io/PrintStream
#39 = Utf8 println
#40 = Utf8 (Ljava/lang/String;)V
複製程式碼
在常量池之後緊接著兩個位元組代表訪問標誌,用於識別一些類或者介面層次的訪問資訊
具體的標誌位和含義如下:
名稱 | 標誌值 | 含義 |
ACC_PUBLIC | 0×0001 | 是否為public |
ACC_FINAL | 0x0010 | 是否為final |
ACC_SUPER | 0x0020 | JDK 1.0.2之後編譯出來的類這個標誌都為真 |
ACC_INTERFACE | 0x0200 | 是否為一個介面 |
ACC_ABSTRACT | 0x0400 | 是否為abstract型別 |
ACC_SUPER | 0x0020 | JDK 1.0.2之後編譯出來的類這個標誌都為真 |
ACC_SYNTHETIC | 0x1000 | 標識這個類並非由使用者程式碼產生 |
ACC_ANNOTATION | 0x2000 | 是否是註解 |
ACC_ENUM | 0x4000 | 是否是列舉 |
沒有使用到的標誌位要求一律為0,本例access_flags的值為:ACC_PUBLIC | ACC_SUPER = 0x0021
類索引、父類索引與介面索引(指向常量池)都是u2型別的資料,除了java.lang.Object
之外所有的Java類都有父類,沒有實現介面計數器為0,本例:
0006 //this_class Person
0007 //super_class java/lang/Object
0000 //沒有實現結構故0
複製程式碼
欄位表用於描述類和介面中宣告的變數。欄位包括類級變數和例項級變數,但是不包括方法中的變數。欄位資訊:欄位的作用域,public/private/protected 例項變數還是類變數,static 可變性,final 併發可見性, volatile 可否被序列化, transient,欄位資料型別(基本型別,物件,陣列),欄位名稱
欄位表結構:
型別 | 名稱 | 數量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
對於本例:
0001 //fields_count 欄位容量即1個欄位
0002 //訪問標誌 private
0008 //常量池第8項,即age
0009 //欄位描述符,常量池第9項,即I
0000 //attribute_count
複製程式碼
Class檔案儲存格式中對方法的描述與對欄位的描述幾乎採用完全一致的方式。
方法表結構:
型別 | 名稱 | 數量 |
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
對於本例:
0004 //方法容量,即4個方法:例項構造器、getAge()、work以及main方法 0001 //方法訪問標誌,public 000A //常量池第10項, 000B //方法描述常量池第11個,()V,沒返回值 0001 //attribute_count 000C //常量池第12項,Code屬性表,存放方法裡的Java程式碼 0000002F //屬性表長度 47 ... 47個位元組後
0001 //方法訪問標誌,public 0011 //常量池第17項,getAge 0012 //方法描述常量池第18個,()I 返回int型 0001 //attribute_count 000C //常量池第12項,Code屬性表,存放方法裡的Java程式碼 0000002F //屬性表長度 47 ... 47個位元組後 0029 // ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED 0×0001|0×0008|0×0020 0013 //常量池第19項,work 000B //方法描述常量池第11個,()V,沒返回值 0001 //attribute_count 000C //常量池第12項,Code屬性表,存放方法裡的Java程式碼 00000025 //屬性表長度 37 ...37個位元組後 0009 //ACC_PUBLIC, ACC_STATIC 0×0001|0×0008 0014 //常量池第20項,main 0015 //方法描述常量池第21項,([Ljava/lang/String;)V String陣列形參,無返回型別方法 0001 //attribute_count 000C //常量池第12項,Code屬性表,存放方法裡的Java程式碼 0000002B //屬性表長度 43 複製程式碼
複製程式碼
複製程式碼
對於本例:
構造方法:
000C //常量池第12項,Code屬性
0000002F //Code屬性表長度 47
0001 //max_stack 運算元棧深度最大值 1
0001 //max_locals 區域性變數儲存
00000005 //code_length 位元組碼長度
2A B7 00 01 B1 //位元組碼指令
0000 //exception_table_length
0002 //attributes_count 2個屬性
000D //常量池第13項, LineNumberTable屬性
00000006 //LineNumberTable屬性表長度
0001 //line_number_table_length
0000 //start_pc 位元組碼行號
0001 //line_number Java原始碼行號
000E //常量池第14項,LocalVariableTable屬性
0000000C //attribute_length
0001 //local_variable_table_length
0000 //start_pc 這個區域性變數的生命週期開始的位元組碼偏移量
0005 //區域性變數作用範圍覆蓋的長度
000F //name_index 區域性變數名稱 常量池第15項,this
0010 //descriptor_index 區域性變數描述 常量池第16項,LPerson;
0000 //這個區域性變數在棧幀區域性變數表中Slot的位置
複製程式碼
getAge方法
000C //常量池第12項,Code屬性
0000002F //attribute_length
0001 //max_stack 運算元棧深度最大值 1
0001 //max_locals 區域性變數儲存
00000005 //code_length 位元組碼長度
2A B4 00 02 AC //位元組碼指令
0000 //exception_table_length
0002 //attributes_count 2個屬性
000D //常量池第13項, LineNumberTable屬性
00000006 //LineNumberTable屬性表長度
0001 //line_number_table_length
0000 //start_pc 位元組碼行號
0006 //line_number Java原始碼行號
000E //常量池第14項,LocalVariableTable屬性
0000000C //attribute_length
0001 //local_variable_table_length
0000 //start_pc 這個區域性變數的生命週期開始的位元組碼偏移量
0005 //區域性變數作用範圍覆蓋的長度
000F //name_index 區域性變數名稱 常量池第15項,this
0010 //descriptor_index 區域性變數描述 常量池第16項,LPerson;
0000 //這個區域性變數在棧幀區域性變數表中Slot的位置
複製程式碼
work方法
000C //常量池第12項,Code屬性
00000025 //attribute_length
0002 //max_stack 運算元棧深度最大值 2
0000 //max_locals 區域性變數儲存
00000009 //code_length 位元組碼長度
B2 00 03 12 04 B6 00 05 B1 //位元組碼指令
0000 //exception_table_length
0001 //attributes_count 1個屬性
000D //常量池第13項, LineNumberTable屬性
0000000A //LineNumberTable屬性表長度
0002 //line_number_table_length 2個line_number_info
0000 //start_pc 位元組碼行號
000A //line_number Java原始碼行號
0008 //start_pc 位元組碼行號
000B //line_number Java原始碼行號
複製程式碼
main方法
000C //常量池第12項,Code屬性
0000002B //attribute_length
0000 //max_stack 運算元棧深度最大值 0
0001 //max_locals 區域性變數儲存
00000001 //code_length 位元組碼長度
B1 //位元組碼指令
0000 //exception_table_length
0002 //attributes_count 2個屬性
000D //常量池第13項, LineNumberTable屬性
00000006 //LineNumberTable屬性表長度
0001 //line_number_table_length 1個line_number_info
0000 //start_pc 位元組碼行號
000F //line_number Java原始碼行號
000E //常量池第14項,LocalVariableTable屬性
0000000C //attribute_length
0001 //local_variable_table_length
0000 //start_pc 這個區域性變數的生命週期開始的位元組碼偏移量
0001 //區域性變數作用範圍覆蓋的長度
0016 //name_index 區域性變數名稱 常量池第22項,args
0017 //descriptor_index 區域性變數描述 常量池第23項,[Ljava/lang/String;
0000 //這個區域性變數在棧幀區域性變數表中Slot的位置
複製程式碼
總結
本篇做了一個小小的嘗試,按照資料項表格一一解析,感興趣的同學可以讀下《深入理解Java虛擬機器》這本聖書。
感謝
《深入理解Java虛擬機器》