JVM學(xué)習(xí)筆記——Class類文件解讀

簡(jiǎn)述

Java源代碼通過編譯生成.class文件字節(jié)碼后再被JVM解釋轉(zhuǎn)化為目標(biāo)機(jī)器代碼,從而實(shí)現(xiàn)一次編寫到處,到處運(yùn)行("Write Once,Run Anywhere")。字節(jié)碼與平臺(tái)無(wú)關(guān),而且并不是只有Java語(yǔ)言編譯為字節(jié)碼文件在虛擬機(jī)上運(yùn)行。

類文件的結(jié)構(gòu)

Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊地排列在Class文件中。Class文件只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表。

無(wú)符號(hào)數(shù)屬于基本的數(shù)據(jù)類型,有u1, u2, u4, u8,分別代表1個(gè)字節(jié)、2個(gè)字節(jié)、4個(gè)字節(jié)和8個(gè)字節(jié)的無(wú)符號(hào)數(shù)

整個(gè)Class文件就是一張表,由以下數(shù)據(jù)項(xiàng)構(gòu)成:

類型 名稱 數(shù)量 u4 magic(魔數(shù)) 1 u2 minor_version(次版本號(hào)) 1 u2 major_version(主版本號(hào)) 1 u2 constant_pool_count(常量池容量) 1 cp_info constant_pool(常量池) constant_pool_count-1 u2 access_flags(訪問標(biāo)志) 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 小試牛刀

寫個(gè)簡(jiǎn)單實(shí)體類,javac編譯后,查看其字節(jié)碼

源碼

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) { }}

十六進(jìn)制Class文件

根據(jù)上述數(shù)據(jù)項(xiàng)表格我們按順序拆分

魔數(shù)

魔數(shù)站每個(gè)Class文件的頭4個(gè)字節(jié),其作用未確定確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件?示例中CA FE BA BE為魔數(shù)

版本號(hào)

魔數(shù)后面緊跟著版本號(hào)

00 00——次版本號(hào)

00 34——主版本號(hào)

根據(jù)如下:

十進(jìn)制版本號(hào) 主版本 jdk1.8 52 jdk1.7 51 jdk1.6 50 jdk1.5 49 jdk1.4 48 jdk1.3 47 jdk1.2 46 jdk1.1 45 十六進(jìn)制0034,對(duì)應(yīng)十進(jìn)制52,對(duì)應(yīng)jdk1.8版本

常量池

常量池可以理解為Class文件之中的資源倉(cāng)庫(kù),它是Class文件結(jié)構(gòu)中與其他項(xiàng)目關(guān)聯(lián)最多的數(shù)據(jù)類型。常量池中主要存放兩大類常量:字面量和符號(hào)引用.

字面量——接近于Java中的常量概念,eg:final修飾、文本字符串

符號(hào)引用——編譯原理概念:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符

常量池中的每一項(xiàng)常量都是一個(gè)表,每種常量都有自己的結(jié)構(gòu),14種常量含義:

類型 標(biāo)志 描述 CONSTANT_utf8_info 1 utf-8編碼的字符串 CONSTANT_Integer_info 3 整型字面量 CONSTANT_Float_info 4 浮點(diǎn)型字面量 CONSTANT_Long_info 5 長(zhǎng)整型字面量 CONSTANT_Double_info 6 雙精度浮點(diǎn)型字面量 CONSTANT_Class_info 7 類或者接口的符號(hào)引用 CONSTANT_String_info 8 字符串型字面量 CONSTANT_Fieldref_info 9 字段的符號(hào)引用 CONSTANT_Methodref_info 10 類中方法的符號(hào)引用 CONSTANT_InterfaceMethoderf_info 11 接口中方法的符號(hào)引用 CONSTANT_NameAndType_info 12 字段或方法的部分符號(hào)引用 CONSTANT_MethodHandle_info 15 表示方法句柄 CONSTANT_MethodType_info 16 標(biāo)識(shí)方法類型 CONSTANT_InvokeDynamic_info 18 表示一個(gè)動(dòng)態(tài)方法調(diào)用點(diǎn) 0×0029轉(zhuǎn)十進(jìn)制為41,代表常量池中有40項(xiàng)常量(容量計(jì)數(shù)是從1而不是0開始。第0項(xiàng)常量空出來(lái)是表達(dá)“不引用任何一個(gè)常量池項(xiàng)目”)

0A即十進(jìn)制10,對(duì)應(yīng)表中CONSTANT_Methodref_info,其結(jié)構(gòu)如下:

類型 名稱 描述 u1 tag 值為10 u2 index 指向聲明方法的類描述符CONSTANT_Class_info的索引項(xiàng) u2 index 指向名稱及類型描述符CONSTANT_NameAndType_info的索引項(xiàng) 0×0007為常量池中第7項(xiàng)CONSTANT_Class_info,0×001A為第26項(xiàng)CONSTANT_NameAndType_info。按照《深入理解Java虛擬機(jī)》第二版,172頁(yè)中表6-6順序解析得:加群617434785里面有文中整理的知識(shí)點(diǎn)

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個(gè)字節(jié),age #9、01 0001 49 //CONSTANT_Utf8_info,1個(gè)字節(jié),I#10、01 0006 3C 69 6E 69 74 3E //CONSTANT_Utf8_info,6個(gè)字節(jié),#11、01 0003 28 29 56 //CONSTANT_Utf8_info,3個(gè)字節(jié),()V#12、01 0004 43 6F 64 65 //CONSTANT_Utf8_info,4個(gè)字節(jié),Code#13、01 000F 4C 69 6E 65 4E 75 6D 62 //CONSTANT_Utf8_info,15個(gè)字節(jié),LineNumberTable 65 72 54 61 62 6C 65 #14、01 0012 4C 6F 63 61 6C 56 61 72 //CONSTANT_Utf8_info,18個(gè)字節(jié),LocalVariableTable 69 61 62 6C 65 54 61 62 6C 65#15、01 0004 74 68 69 73 //CONSTANT_Utf8_info,4個(gè)字節(jié),this#16、01 0008 4C 50 65 72 73 6F 6E 3B //CONSTANT_Utf8_info,8個(gè)字節(jié),LPerson;#17、01 0006 67 65 74 41 67 65 //CONSTANT_Utf8_info,6個(gè)字節(jié),getAge#18、01 0003 28 29 49 //CONSTANT_Utf8_info,3個(gè)字節(jié),()I#19、01 0004 77 6F 72 6B //CONSTANT_Utf8_info,4個(gè)字節(jié),work#20、01 0004 6D 61 69 6E //CONSTANT_Utf8_info,4個(gè)字節(jié),main#21、01 0016 28 5B 4C 6A 61 76 61 2F //CONSTANT_Utf8_info,22個(gè)字節(jié),([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個(gè)字節(jié),args#23、01 0013 5B 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info,19個(gè)字節(jié),[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個(gè)字節(jié), SourceFile 6C 65#25、01 000B 50 65 72 73 6F 6E 2E 6A //CONSTANT_Utf8_info,11個(gè)字節(jié), 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個(gè)字節(jié),工作#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個(gè)字節(jié),Person#34、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info,16個(gè)字節(jié),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個(gè)字節(jié), java/lang/System 67 2F 53 79 73 74 65 6D#36、01 0003 6F 75 74 //CONSTANT_Utf8_info,3個(gè)字節(jié),out#37、01 0015 4C 6A 61 76 61 2F 69 6F //CONSTANT_Utf8_info,21個(gè)字節(jié), 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個(gè)字節(jié),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個(gè)字節(jié),println#40、01 0015 28 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info,21個(gè)字節(jié), (Ljava/lang/String;)V 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56

也可以java -verbose分析Class文件字節(jié)碼,得到結(jié)果:

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

訪問標(biāo)志

在常量池之后緊接著兩個(gè)字節(jié)代表訪問標(biāo)志,用于識(shí)別一些類或者接口層次的訪問信息

具體的標(biāo)志位和含義如下:

名稱 標(biāo)志值 含義 ACC_PUBLIC 0×0001 是否為public ACC_FINAL 0x0010 是否為final ACC_SUPER 0x0020 JDK 1.0.2之后編譯出來(lái)的類這個(gè)標(biāo)志都為真 ACC_INTERFACE 0x0200 是否為一個(gè)接口 ACC_ABSTRACT 0x0400 是否為abstract類型 ACC_SUPER 0x0020 JDK 1.0.2之后編譯出來(lái)的類這個(gè)標(biāo)志都為真 ACC_SYNTHETIC 0x1000 標(biāo)識(shí)這個(gè)類并非由用戶代碼產(chǎn)生 ACC_ANNOTATION 0x2000 是否是注解 ACC_ENUM 0x4000 是否是枚舉 沒有使用到的標(biāo)志位要求一律為0,本例access_flags的值為:ACC_PUBLIC | ACC_SUPER = 0x0021

類索引、父類索引與接口索引

類索引、父類索引與接口索引(指向常量池)都是u2類型的數(shù)據(jù),除了java.lang.Object 之外所有的Java類都有父類,沒有實(shí)現(xiàn)接口計(jì)數(shù)器為0,本例:

0006 //this_class Person 0007 //super_class java/lang/Object 0000 //沒有實(shí)現(xiàn)結(jié)構(gòu)故0

字段表集合

字段表用于描述類和接口中聲明的變量。字段包括類級(jí)變量和實(shí)例級(jí)變量,但是不包括方法中的變量。字段信息:字段的作用域,public/private/protected 實(shí)例變量還是類變量,static 可變性,final 并發(fā)可見性, volatile 可否被序列化, transient,字段數(shù)據(jù)類型(基本類型,對(duì)象,數(shù)組),字段名稱

字段表結(jié)構(gòu):

類型 名稱 數(shù)量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_count

對(duì)于本例:

0001 //fields_count 字段容量即1個(gè)字段 0002 //訪問標(biāo)志 private 0008 //常量池第8項(xiàng),即age 0009 //字段描述符,常量池第9項(xiàng),即I 0000 //attribute_count

方法表集合

Class文件存儲(chǔ)格式中對(duì)方法的描述與對(duì)字段的描述幾乎采用完全一致的方式。

方法表結(jié)構(gòu):

類型 名稱 數(shù)量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_count

對(duì)于本例:

0004 //方法容量,即4個(gè)方法:實(shí)例構(gòu)造器、getAge()、work以及main方法 0001 //方法訪問標(biāo)志,public 000A //常量池第10項(xiàng), 000B //方法描述常量池第11個(gè),()V,沒返回值 0001 //attribute_count 000C //常量池第12項(xiàng),Code屬性表,存放方法里的Java代碼 0000002F //屬性表長(zhǎng)度 47 ... 47個(gè)字節(jié)后0001 //方法訪問標(biāo)志,public0011 //常量池第17項(xiàng),getAge0012 //方法描述常量池第18個(gè),()I 返回int型0001 //attribute_count000C //常量池第12項(xiàng),Code屬性表,存放方法里的Java代碼0000002F //屬性表長(zhǎng)度 47... 47個(gè)字節(jié)后0029 // ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED 0×0001|0×0008|0×00200013 //常量池第19項(xiàng),work000B //方法描述常量池第11個(gè),()V,沒返回值0001 //attribute_count000C //常量池第12項(xiàng),Code屬性表,存放方法里的Java代碼00000025 //屬性表長(zhǎng)度 37...37個(gè)字節(jié)后0009 //ACC_PUBLIC, ACC_STATIC 0×0001|0×00080014 //常量池第20項(xiàng),main0015 //方法描述常量池第21項(xiàng),([Ljava/lang/String;)V String數(shù)組形參,無(wú)返回類型方法0001 //attribute_count000C //常量池第12項(xiàng),Code屬性表,存放方法里的Java代碼0000002B //屬性表長(zhǎng)度 43

屬性表集合

對(duì)于本例:

構(gòu)造方法:

000C //常量池第12項(xiàng),Code屬性 0000002F //Code屬性表長(zhǎng)度 47 0001 //max_stack 操作數(shù)棧深度最大值 1 0001 //max_locals 局部變量存儲(chǔ) 00000005 //code_length 字節(jié)碼長(zhǎng)度 2A B7 00 01 B1 //字節(jié)碼指令 0000 //exception_table_length 0002 //attributes_count 2個(gè)屬性 000D //常量池第13項(xiàng), LineNumberTable屬性 00000006 //LineNumberTable屬性表長(zhǎng)度 0001 //line_number_table_length 0000 //start_pc 字節(jié)碼行號(hào) 0001 //line_number Java源碼行號(hào) 000E //常量池第14項(xiàng),LocalVariableTable屬性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 這個(gè)局部變量的生命周期開始的字節(jié)碼偏移量 0005 //局部變量作用范圍覆蓋的長(zhǎng)度 000F //name_index 局部變量名稱 常量池第15項(xiàng),this 0010 //descriptor_index 局部變量描述 常量池第16項(xiàng),LPerson; 0000 //這個(gè)局部變量在棧幀局部變量表中Slot的位置

getAge方法

000C //常量池第12項(xiàng),Code屬性 0000002F //attribute_length 0001 //max_stack 操作數(shù)棧深度最大值 1 0001 //max_locals 局部變量存儲(chǔ) 00000005 //code_length 字節(jié)碼長(zhǎng)度 2A B4 00 02 AC //字節(jié)碼指令 0000 //exception_table_length 0002 //attributes_count 2個(gè)屬性 000D //常量池第13項(xiàng), LineNumberTable屬性 00000006 //LineNumberTable屬性表長(zhǎng)度 0001 //line_number_table_length 0000 //start_pc 字節(jié)碼行號(hào) 0006 //line_number Java源碼行號(hào) 000E //常量池第14項(xiàng),LocalVariableTable屬性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 這個(gè)局部變量的生命周期開始的字節(jié)碼偏移量 0005 //局部變量作用范圍覆蓋的長(zhǎng)度 000F //name_index 局部變量名稱 常量池第15項(xiàng),this 0010 //descriptor_index 局部變量描述 常量池第16項(xiàng),LPerson; 0000 //這個(gè)局部變量在棧幀局部變量表中Slot的位置

work方法

000C //常量池第12項(xiàng),Code屬性 00000025 //attribute_length 0002 //max_stack 操作數(shù)棧深度最大值 2 0000 //max_locals 局部變量存儲(chǔ) 00000009 //code_length 字節(jié)碼長(zhǎng)度 B2 00 03 12 04 B6 00 05 B1 //字節(jié)碼指令 0000 //exception_table_length 0001 //attributes_count 1個(gè)屬性 000D //常量池第13項(xiàng), LineNumberTable屬性 0000000A //LineNumberTable屬性表長(zhǎng)度 0002 //line_number_table_length 2個(gè)line_number_info 0000 //start_pc 字節(jié)碼行號(hào) 000A //line_number Java源碼行號(hào) 0008 //start_pc 字節(jié)碼行號(hào) 000B //line_number Java源碼行號(hào)

main方法

000C //常量池第12項(xiàng),Code屬性 0000002B //attribute_length 0000 //max_stack 操作數(shù)棧深度最大值 0 0001 //max_locals 局部變量存儲(chǔ) 00000001 //code_length 字節(jié)碼長(zhǎng)度 B1 //字節(jié)碼指令 0000 //exception_table_length 0002 //attributes_count 2個(gè)屬性 000D //常量池第13項(xiàng), LineNumberTable屬性 00000006 //LineNumberTable屬性表長(zhǎng)度 0001 //line_number_table_length 1個(gè)line_number_info 0000 //start_pc 字節(jié)碼行號(hào) 000F //line_number Java源碼行號(hào) 000E //常量池第14項(xiàng),LocalVariableTable屬性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 這個(gè)局部變量的生命周期開始的字節(jié)碼偏移量 0001 //局部變量作用范圍覆蓋的長(zhǎng)度 0016 //name_index 局部變量名稱 常量池第22項(xiàng),args 0017 //descriptor_index 局部變量描述 常量池第23項(xiàng),[Ljava/lang/String; 0000 //這個(gè)局部變量在棧幀局部變量表中Slot的位置

總結(jié)

本篇做了一個(gè)小小的嘗試,按照數(shù)據(jù)項(xiàng)表格一一解析,感興趣的同學(xué)可以讀下《深入理解Java虛擬機(jī)》這本圣書。

如果想學(xué)習(xí)Java工程化、高性能及分布式、深入淺出。微服務(wù)、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java進(jìn)階群:617434785,群里有阿里大牛直播講解技術(shù),以及Java大型互聯(lián)網(wǎng)技術(shù)的視頻免費(fèi)分享給大家。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容