簡述
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順序解析得:加群617434785里面有文中整理的知識點
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 //方法訪問標志,public0011 //常量池第17項,getAge0012 //方法描述常量池第18個,()I 返回int型0001 //attribute_count000C //常量池第12項,Code屬性表,存放方法里的Java代碼0000002F //屬性表長度 47... 47個字節后0029 // ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED 0×0001|0×0008|0×00200013 //常量池第19項,work000B //方法描述常量池第11個,()V,沒返回值0001 //attribute_count000C //常量池第12項,Code屬性表,存放方法里的Java代碼00000025 //屬性表長度 37...37個字節后0009 //ACC_PUBLIC, ACC_STATIC 0×0001|0×00080014 //常量池第20項,main0015 //方法描述常量池第21項,([Ljava/lang/String;)V String數組形參,無返回類型方法0001 //attribute_count000C //常量池第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工程化、高性能及分布式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java進階群:617434785,群里有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給大家。