JVM实战与原理---Class文件结构
JVM實戰與原理
目錄
Class文件結構
1. 數據結構
2. class文件結構詳解
2.1 魔數與Class文件的版本
2.2 常量池
2.3 訪問標志
2.4 類索引、父類索引與接口索引集合
2.5 字段表集合
2.6 方法表集合
2.7?屬性表集合
Class文件結構
章節目的:介紹class文件的結構
引言:當使用javac命令對java文件進行編譯后,便生成了Class文件,那么Class文件的結構是怎么樣的呢?
下面是Class文件結構的介紹。
1. 數據結構
首先,我們需要知道Class文件的數據結構如下,其中u1、u2、u4分別代表1個字節、2個字節、4個字節,info結尾的代表一個復合結構的數據
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]; }2. class文件結構詳解
接著,我們繼續通過以下這段java代碼來分析
class Person {private static String name = "wenxl";public static void main(String[] args) {System.out.println(name);} }對于已經編譯生成了class文件,此時將class文件通過winHex打開,會有如下十六進制的字節碼,例如首個CA就為一個字節,即為u1。
Offset 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1500000000 CA FE BA BE 00 00 00 34 00 22 0A 00 07 00 13 09 漱壕 4 " 00000016 00 14 00 15 09 00 06 00 16 0A 00 17 00 18 08 00 00000032 19 07 00 1A 07 00 1B 01 00 04 6E 61 6D 65 01 00 name 00000048 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 Ljava/lang/Stri 00000064 6E 67 3B 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 ng; <init> ( 00000080 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 )V Code Line 00000096 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 04 6D 61 NumberTable ma 00000112 69 6E 01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E in ([Ljava/lan 00000128 67 2F 53 74 72 69 6E 67 3B 29 56 01 00 08 3C 63 g/String;)V <c 00000144 6C 69 6E 69 74 3E 01 00 0A 53 6F 75 72 63 65 46 linit> SourceF 00000160 69 6C 65 01 00 0B 50 65 72 73 6F 6E 2E 6A 61 76 ile Person.jav 00000176 61 0C 00 0A 00 0B 07 00 1C 0C 00 1D 00 1E 0C 00 a 00000192 08 00 09 07 00 1F 0C 00 20 00 21 01 00 05 77 65 ! we 00000208 6E 78 6C 01 00 06 50 65 72 73 6F 6E 01 00 10 6A nxl Person j 00000224 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 ava/lang/Object 00000240 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 java/lang/Syst 00000256 65 6D 01 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 em out Ljava 00000272 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B /io/PrintStream; 00000288 01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 java/io/Print 00000304 53 74 72 65 61 6D 01 00 07 70 72 69 6E 74 6C 6E Stream println 00000320 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 (Ljava/lang/S 00000336 74 72 69 6E 67 3B 29 56 00 20 00 06 00 07 00 00 tring;)V 00000352 00 01 00 0A 00 08 00 09 00 00 00 03 00 00 00 0A 00000368 00 0B 00 01 00 0C 00 00 00 1D 00 01 00 01 00 00 00000384 00 05 2A B7 00 01 B1 00 00 00 01 00 0D 00 00 00 *? ? 00000400 06 00 01 00 00 00 01 00 09 00 0E 00 0F 00 01 00 00000416 0C 00 00 00 26 00 02 00 01 00 00 00 0A B2 00 02 & ? 00000432 B2 00 03 B6 00 04 B1 00 00 00 01 00 0D 00 00 00 ? ? ? 00000448 0A 00 02 00 00 00 06 00 09 00 07 00 08 00 10 00 00000464 0B 00 01 00 0C 00 00 00 1E 00 01 00 00 00 00 00 00000480 06 12 05 B3 00 03 B1 00 00 00 01 00 0D 00 00 00 ? ? 00000496 06 00 01 00 00 00 03 00 01 00 11 00 00 00 02 00 00000512 122.1 魔數與Class文件的版本
ClassFile {u4 magic;u2 minor_version;u2 major_version;....... }對應的字節碼為:CA FE BA BE 00 00 00 34
作用:每個Class文件的頭4個字節稱為魔數magic,作用是確定這個文件是否為JVM能接受的Clcass文件,固定為0xCAFEBABE。
后4字節說明Class文件的版本
字節碼詳解:00 00 00 34 說明該文件版本為JDK1.8。
2.2 常量池
ClassFile {.......u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1];....... }對應的字節碼為:00 22
作用:前兩個字節說明常量池的數量,后面則是常量池內容。常量池可以理解是Class文件的資源倉庫。
字節碼詳解:其中前兩個字節0x0022轉換為十進制為34,說明有33個常量
cp_info常量類型的數據結構為
cp_info {u1 tag; u1 info[]; }其中tag的值代表當前常量屬于那種常量類型
| Tag Value | Constant Type | DESC | DATA STRUCTURE |
| 1 | CONSTANT_Utf8 | utf-8編碼的字符串 | CONSTANT_Utf8_info { ??? u1 tag; ??? u2 length; ??? u1 bytes[length]; } |
| 3 | CONSTANT_Integer | 整形 | CONSTANT_Integer_info { ??? u1 tag; ??? u4 bytes; } |
| 4 | CONSTANT_Float | 浮點型 | CONSTANT_Float_info { ??? u1 tag; ??? u4 bytes; } |
| 5 | CONSTANT_Long | 長整形 | CONSTANT_Long_info { ??? u1 tag; ??? u4 high_bytes; ??? u4 low_bytes; } |
| 6 | CONSTANT_Double | 雙精度浮點型 | CONSTANT_Double_info { ??? u1 tag; ??? u4 high_bytes; ??? u4 low_bytes; } |
| 7 | CONSTANT_Class | 類或接口的符號引用 | CONSTANT_Class_info { ??? u1 tag; ??? u2 name_index; } |
| 8 | CONSTANT_String | 字符串類型 | CONSTANT_String_info { ??? u1 tag; ??? u2 string_index; } |
| 9 | CONSTANT_Fieldref | 字段的符號引用 | CONSTANT_Fieldref_info { ??? u1 tag; ??? u2 class_index; ??? u2 name_and_type_index; } |
| 10 | CONSTANT_Methodref | 類中方法的符號引用 | CONSTANT_Methodref_info { ??? u1 tag; ??? u2 class_index; ??? u2 name_and_type_index; } |
| 11 | CONSTANT_InterfaceMethodref | 接口中方法的符號引用 | CONSTANT_InterfaceMethodref_info { ??? u1 tag; ??? u2 class_index; ??? u2 name_and_type_index; } |
| 12 | CONSTANT_NameAndType | 字段或方法的部分符號引用 | CONSTANT_NameAndType_info { ??? u1 tag; ??? u2 name_index; ??? u2 descriptor_index; } |
| 15 | CONSTANT_MethodHandle | 表示方法句柄 | CONSTANT_MethodHandle_info { ??? u1 tag; ??? u1 reference_kind; ??? u2 reference_index; } |
| 16 | CONSTANT_MethodType | 標識方法類型 | CONSTANT_MethodType_info { ??? u1 tag; ??? u2 descriptor_index; } |
| 18 | CONSTANT_InvokeDynamic | 表示一個動態方法調用點 | CONSTANT_InvokeDynamic_info { ??? u1 tag; ??? u2 bootstrap_method_attr_index; ??? u2 name_and_type_index; } |
我們按字節順序開始分析,第一個tag值為0x0A,對應常量類型為CONSTANT_Methodref,其結構為
CONSTANT_Methodref_info {u1 tag;u2 class_index;u2 name_and_type_index; }對應的字節碼為
0A00 0700?13
0007為class_index指向一個CONSTANT_Class_info
0013為name_and_type_index指向一個CONSTANT_NameAndType
那指向的這兩個又是什么呢?我們往下繼續分析
第二個tag值為0x09,對應常量類型為CONSTANT_Fieldref,其結構為
CONSTANT_Fieldref_info {u1 tag;u2 class_index;u2 name_and_type_index; }對應的字節碼為
0900 1400 15
0014為class_index指向一個CONSTANT_Class_info
0015為name_and_type_index指向一個CONSTANT_NameAndType
我們按這樣的邏輯將常量池字 節碼解析為
| Index | Tag | Info | Tag Value |
| 1 | 0A | 0007 0013 | CONSTANT_Methodref |
| 2 | 09 | 0014 0015 | CONSTANT_Fieldref |
| 3 | 09 | 0006 0016 | CONSTANT_Fieldref |
| 4 | 0A | 0017 0018 | CONSTANT_Methodref |
| 5 | 08 | 0019 | CONSTANT_String |
| 6 | 07 | 001A | CONSTANT_Class |
| 7 | 07 | 001B | CONSTANT_Class |
| 8 | 01 | 0004 6E6D 6561 | CONSTANT_Utf8 |
| 9 | 01 | 0012 4C.. ..3B | CONSTANT_Utf8 |
| 10 | 01 | 0006 3C.. ..3E | CONSTANT_Utf8 |
| 11 | 01 | 0003 2829 56 | CONSTANT_Utf8 |
| 12 | 01 | 0004 436F 6465 | CONSTANT_Utf8 |
| 13 | 01 | 000F 4C.. ..65 | CONSTANT_Utf8 |
| 14 | 01 | 0004 6D61 696E | CONSTANT_Utf8 |
| 15 | 01 | 0016 28.. ..56 | CONSTANT_Utf8 |
| 16 | 01 | 0008 3C.. ..3E | CONSTANT_Utf8 |
| 17 | 01 | 000A 53.. ..65 | CONSTANT_Utf8 |
| 18 | 01 | 000B 50.. ..61 | CONSTANT_Utf8 |
| 19 | 0C | 000A 000B | CONSTANT_NameAndType |
| 20 | 07 | 001C | CONSTANT_Class |
| 21 | 0C | 001D 001E | CONSTANT_NameAndType |
| 22 | 0C | 0008 0009 | CONSTANT_NameAndType |
| 23 | 07 | 001F | CONSTANT_Class |
| 24 | 0C | 0020 0021 | CONSTANT_NameAndType |
| 25 | 01? | 0005 77.. ..6C | CONSTANT_Utf8 |
| 26 | 01? | 0006 50.. ..6E | CONSTANT_Utf8 |
| 27 | 01? | 0010 6A.. ..74 | CONSTANT_Utf8 |
| 28 | 01? | 0010 6A.. ..6D | CONSTANT_Utf8 |
| 29 | 01? | 0003 6F75 74 | CONSTANT_Utf8 |
| 30 | 01? | 0015 4C.. ..3B | CONSTANT_Utf8 |
| 31 | 01? | 0013 6A.. ..6D | CONSTANT_Utf8 |
| 32 | 01? | 0007 70.. ..6E | CONSTANT_Utf8 |
| 33 | 01? | 0015 28.. ..56 | CONSTANT_Utf8 |
這時我們再回到第一個CONSTANT_Methodref,該常量用來描述的方法,其中
0007為class_index指向一個CONSTANT_Class_info,說明是哪個類的方法
0013為name_and_type_index指向一個CONSTANT_NameAndType,說明的是這個方法的方法名與方法的入參及返回值類型
0007即index為7的常量,我們可以看到是
| 7 | 07 | 001B | CONSTANT_Class |
CONSTANT_Class的結構為
CONSTANT_Class_info {u1 tag;u2 name_index; }同理,index為001B的即第27個常量是
| 27 | 01? | 0010 6A.. ..74 | CONSTANT_Utf8 |
CONSTANT_Utf8的結構為
CONSTANT_Utf8_info {u1 tag;u2 length;u1 bytes[length]; }length為0010,即后面跟著長度為16的字符串,6A.. ..74轉換為對應的字符串為java/lang/Object,說明調用的是java/lang/Object的方法
同樣,0013指向第19個常量,是個CONSTANT_NameAndType_info
| 19 | 0C | 000A 000B | CONSTANT_NameAndType |
結構如下
CONSTANT_NameAndType_info {u1 tag;u2 name_index;u2 descriptor_index; }000A指向第10個常量,轉換為對應的字符串為<init>,說明方法名為<init>
| 10 | 01 | 0006 3C.. ..3E | CONSTANT_Utf8 |
000B指向第11個常量,轉換為對應的字符串為()V,說明入參為空,返回值類型是void。
?
我們可以用javap -verbose Person的命令反編譯,就可以得到javap工具幫我們解析的字節碼描述,我們發現其中Constant pool部分與我們分析出的結果是一致的。
Classfile /D:/用戶目錄/我的文檔/Code/JVMSearch/Person.classLast modified 2020-12-19; size 513 bytesMD5 checksum dea507ac0ecc8c9063c778c6f748a2bbCompiled from "Person.java" class Personminor version: 0major version: 52flags: ACC_SUPER Constant pool:#1 = Methodref #7.#19 // java/lang/Object."<init>":()V#2 = Fieldref #20.#21 // java/lang/System.out:Ljava/io/PrintStream;#3 = Fieldref #6.#22 // Person.name:Ljava/lang/String;#4 = Methodref #23.#24 // java/io/PrintStream.println:(Ljava/lang/String;)V#5 = String #25 // wenxl#6 = Class #26 // Person#7 = Class #27 // java/lang/Object#8 = Utf8 name#9 = Utf8 Ljava/lang/String;#10 = Utf8 <init>#11 = Utf8 ()V#12 = Utf8 Code#13 = Utf8 LineNumberTable#14 = Utf8 main#15 = Utf8 ([Ljava/lang/String;)V#16 = Utf8 <clinit>#17 = Utf8 SourceFile#18 = Utf8 Person.java#19 = NameAndType #10:#11 // "<init>":()V#20 = Class #28 // java/lang/System#21 = NameAndType #29:#30 // out:Ljava/io/PrintStream;#22 = NameAndType #8:#9 // name:Ljava/lang/String;#23 = Class #31 // java/io/PrintStream#24 = NameAndType #32:#33 // println:(Ljava/lang/String;)V#25 = Utf8 wenxl#26 = Utf8 Person#27 = Utf8 java/lang/Object#28 = Utf8 java/lang/System#29 = Utf8 out#30 = Utf8 Ljava/io/PrintStream;#31 = Utf8 java/io/PrintStream#32 = Utf8 println#33 = Utf8 (Ljava/lang/String;)V {Person();descriptor: ()Vflags:Code:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: getstatic #3 // Field name:Ljava/lang/String;6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V9: returnLineNumberTable:line 6: 0line 7: 9static {};descriptor: ()Vflags: ACC_STATICCode:stack=1, locals=0, args_size=00: ldc #5 // String wenxl2: putstatic #3 // Field name:Ljava/lang/String;5: returnLineNumberTable:line 3: 0 } SourceFile: "Person.java"2.3 訪問標志
ClassFile {.......u2 access_flags;....... }對應的字節碼為:00 20
作用:用于識別類或者接口層次的訪問信息
字節碼詳解:每個位所代表的意義如下表,因為該類是JDK1.2之后的編譯器進行編譯,且其他標識均為0,故為00 20。大家可以試試在類被聲明為public,abstract,final的情況下,字節碼是如何展示的。
| Flag Name | Value | Interpretation |
| ACC_PUBLIC | 0x0001 | 是否為public類型 |
| ACC_FINAL | 0x0010 | 是否被聲明為final,只有類可設置 |
| ACC_SUPER | 0x0020 | 是否允許使用invokespecial字節碼指令的新語意,JDK1.0.2該指令語意發生改變,故JDK1.0.2后該標志都為真 |
| ACC_INTERFACE | 0x0200 | 標識這是一個接口 |
| ACC_ABSTRACT | 0x0400 | 是否為abstract類型 |
| ACC_SYNTHETIC | 0x1000 | 標識這個類并非由用戶代碼產生 |
| ACC_ANNOTATION | 0x2000 | 標識這是一個注解 |
| ACC_ENUM | 0x4000 | 標識這是一個枚舉 |
2.4 類索引、父類索引與接口索引集合
ClassFile {.......u2 this_class;u2 super_class;u2 interfaces_count;u2 interfaces[interfaces_count];....... }對應的字節碼為:00 06 00 07 00 00
作用:這三項數據確定了這個類的繼承關系,類索引(this_class)確定類的全限定名,父類索引(super_class)確定類的父類的全限定名,接口索引集合(interfaces)描述類實現了哪些接口
字節碼詳解:00 06指向常量池第6個常量,類型為CONSTACT_Class_info,值為Person。
00 07同樣類型為CONSTACT_Class_info,值為java/lang/Object。
00 00說明該類沒實現任何接口。好奇的同學可以試試實現了接口后,這里的字節碼是如何展示的。
2.5 字段表集合
ClassFile {.......u2 fields_count;field_info fields[fields_count];....... }對應的字節碼為:00 01?00 0A?00 08 00 09?00 00
作用:描述接口或者類中的聲明的變量。
字節碼詳解:
其中前兩個字節0x0001轉換為十進制為1,說明有1個字段。
field_info常量類型的數據結構為
field_info {u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count]; }access_flag為字段訪問標志,含義表如下。字節碼為00 0A,說明ACC_PRIVATE和ACC_STATIC為真,即該字段為private和static
| Flag Name | Value | Interpretation |
| 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 | 字段是否由編譯器自動產生的 |
| ACC_ENUM | 0x4000 | 字段是否enum |
name_index代表字段的簡單名稱,descriptor_index代表字段和方法的描述符。
字節碼為00 08 00 09,指向常量池第8和第9個常量,類型為CONSTACT_Utf8_info,值分別為name和Ljava/lang/String;。
說明該字段名為name,描述符為java/lang/String。
attribute_info則是該字段的屬性表,字節碼為00 00, 說明沒有屬性。
2.6 方法表集合
ClassFile {.......u2 methods_count;method_info methods[methods_count];....... }對應的字節碼為:00 03?00 00?00 0A 00 0B 00 01
作用:描述接口或者類中的方法。
字節碼詳解:
其中前兩個字節0x0003轉換為十進制為3,說明有3個方法。
method_info常量類型的數據結構為
method_info {u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count]; }access_flag為字段訪問標志,含義表如下。字節碼為00 00,說明標志均為假
| Flag Name | Value | Interpretation |
| 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 | 方法是否是由編譯器產生的橋接方法 |
| ACC_VARARGS | 0x0080 | 方法是否接受不定參數 |
| ACC_NATIVE | 0x0100 | 方法是否native |
| ACC_ABSTRACT | 0x0400 | 方法是否abstract |
| ACC_STRICT | 0x0800 | 方法是否strictfp |
| ACC_SYNTHETIC | 0x1000 | 方法是否是由編譯器自動產生的 |
字節碼為00 0A 00 0B,指向常量池第10和第11個常量,類型為CONSTACT_Utf8_info,值分別為<init>和()V。name_index代表方法名,descriptor_index代表方法的描述符。
說明該方法名為<init>,描述符為()V。
attribute_info則是該字段的屬性表,那么屬性表是什么樣的結構呢。
2.7?屬性表集合
ClassFile {u2 attributes_count;attribute_info attributes[attributes_count];....... }對應的字節碼為:00 01?00 0C?00 00 00 1D
作用:Class文件、字段表、方法表都可以攜帶自己的屬性表集合,用于描述某些場景專有的信息
字節碼詳解:
其中前兩個字節0x0001轉換為十進制為1,說明有1個屬性。
attribute_info常量類型的數據結構為
attribute_info {u2 attribute_name_index;u4 attribute_length;u1 info[attribute_length]; }attribute_name_index為屬性名。字節碼為00 0C,指向常量池第12個常量,類型為CONSTACT_Utf8_info,值為Code,說明這是一個Code屬性。
00 00 00 1D說明后面屬性內容的長度,1D為十進制的29,說明后面29個字節的內容為屬性內容。那么這個Code屬性的屬性作用和結構內容是怎么樣的呢?
Code屬性作用:方法中的代碼經過編譯器變為的字節碼指令,存儲在Code屬性內。
Code屬性表結構:
字節碼詳解:00 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01?00 0D
這里從max_stack開始,max_stack描述了操作數棧深度的最大值,這里是00 01說明最大值為1
max_locals描述了局部變量表所需的存儲空間,單位是Slot,對于double和long這兩種64位的需要兩個Slot存放,其余的byte、char、float、int、short、boolean和returnAddress等長度不超過32位的則用1個Slot存放。
Slot可以被重用,當代碼執行超過局部變量的作用域,該變量占用的Slot可以被其他Slot占用,所以max_locals不等于方法用到了多少個局部變量,而是編譯器根據變量的作用域計算出max_locals的大小。
code_length和code存儲Java源程序編譯后生成的字節碼指令,code_length為00 05,說明后面有5個字節為字節碼指令,為2A B7 00 01 B1,根據指令表,2A對應指令為aload_0,含義是將第0個Slot中為reference類型的本地變量推送到操作數棧頂;B7對應指令為invokespecial,作用是以棧頂的reference類型的數據所指向的對象作為方法接收者,調用對象的實例構造方法,后跟兩個字節是他的參數,說明調用哪一個方法;00 01是參數,指向常量池第1個常量,類型為CONSTACT_Methodref_info,值為java/lang/Object."<init>":()V;B1對應指令為return,含義是返回此方法,且返回值為void,執行該條指令后,方法結束。
exception_table用于實現Java一場及finally處理機制,00 00說明定義了0個異常。
最后的是attribute_info,說明Code屬性的屬性,00 01說明有1個屬性,結構是一樣的。
字節碼為00 0D,指向常量池第13個常量,類型為CONSTACT_Utf8_info,值為LineNumberTable,說明這是一個LineNumberTable屬性。
LineNumberTable屬性作用:描述Java源碼行號與字節碼行號之間的對應關系
LineNumberTable屬性表結構:
字節碼詳解:00 00 00 06 00 01 00?00 00 01
00 00 00 06說明后面6個字節為屬性描述。
line_number_table_length說明line_number_table長度,00 01說明后續有1個line_number_table_length。
line_number_table包含兩個數據項,其中start_pc是字節碼行號,line_number是Java源碼行號。
00?00 00 01說明行號數據是0:1。
還有很多屬性,如下。
每個屬性都有不同的結構及作用,讀者可以自行修改源碼,從而發現不同的屬性。
| 屬性名稱 | 使用位置 | 含義 |
| Code? | 方法表 | Java代碼編譯成的字節碼指令 |
| ConstantValue | 字段表 | final關鍵字定義的常量值 |
| Deprecated | 類、方法表、字段表 | 被聲明為deprecated的方法和字段 |
| Exceptions | 方法表 | 方法拋出的異常 |
| EnclosingMethod | 類文件 | 僅當一個類為局部類或者匿名類時才能擁有這個屬性,這個屬性用于標識這個類所在的外圍方法 |
| InnerClasses | 類文件 | 內部類列表 |
| LineNumberTable | Code屬性 | Java源碼的行號與字節碼指令的對應關系 |
| LocalVariableTable | Code屬性 | 方法的局部變量描述 |
| StackMapTable | Code屬性 | JDK1.6新增屬性,供新的類型檢查驗證器檢查和處理目標方法的局部變量和操作數棧所需要的類型是否匹配 |
| Signature | 類、方法表、字段表 | 用于支持泛型情況下的方法簽名 |
| SourceFile | 類文件 | 記錄源文件名稱 |
| SourceDebugExtension | 類文件 | 存儲額外的調試信息 |
| Synthetic | 類、方法表、字段表 | 標識方法或字段為編譯器自動生成的 |
| LocalVariableTypeTable | 類 | 使用特征簽名代替描述符,是為了引入泛型語法之后能描述泛型參數化類型而添加 |
| RuntimeVisibleAnnotations | 類、方法表、字段表 | 為動態注解提供支持,指明哪些注解是運行時可見的 |
| RuntimeInvisibleAnnotations | 類、方法表、字段表 | 指明哪些注解是運行時不可見的 |
| RuntimeVisibleParameterAnnotations | 方法表 | 與RuntimeVisibleAnnotations類似,作用對象為方法參數 |
| RuntimeInvisibleParameterAnnotations | 方法表 | 與RuntimeInvisibleAnnotations類似,作用對象為方法參數 |
| AnootationDefault | 方法表 | 記錄注解類元素的默認值 |
| BootstrapMethods | 類文件 | 保存invokedynamic指令引用的引導方法限定符 |
后面的字節碼也無需在分析下去了,后面還有兩個方法,一個屬性,按照這個結構繼續解析下去,就可以發現,跟javap反編譯出來的描述是一致的。
Classfile /D:/用戶目錄/我的文檔/Code/JVMSearch/Person.classLast modified 2020-12-19; size 513 bytesMD5 checksum dea507ac0ecc8c9063c778c6f748a2bbCompiled from "Person.java" class Personminor version: 0major version: 52flags: ACC_SUPER Constant pool:#1 = Methodref #7.#19 // java/lang/Object."<init>":()V#2 = Fieldref #20.#21 // java/lang/System.out:Ljava/io/PrintStream;#3 = Fieldref #6.#22 // Person.name:Ljava/lang/String;#4 = Methodref #23.#24 // java/io/PrintStream.println:(Ljava/lang/String;)V#5 = String #25 // wenxl#6 = Class #26 // Person#7 = Class #27 // java/lang/Object#8 = Utf8 name#9 = Utf8 Ljava/lang/String;#10 = Utf8 <init>#11 = Utf8 ()V#12 = Utf8 Code#13 = Utf8 LineNumberTable#14 = Utf8 main#15 = Utf8 ([Ljava/lang/String;)V#16 = Utf8 <clinit>#17 = Utf8 SourceFile#18 = Utf8 Person.java#19 = NameAndType #10:#11 // "<init>":()V#20 = Class #28 // java/lang/System#21 = NameAndType #29:#30 // out:Ljava/io/PrintStream;#22 = NameAndType #8:#9 // name:Ljava/lang/String;#23 = Class #31 // java/io/PrintStream#24 = NameAndType #32:#33 // println:(Ljava/lang/String;)V#25 = Utf8 wenxl#26 = Utf8 Person#27 = Utf8 java/lang/Object#28 = Utf8 java/lang/System#29 = Utf8 out#30 = Utf8 Ljava/io/PrintStream;#31 = Utf8 java/io/PrintStream#32 = Utf8 println#33 = Utf8 (Ljava/lang/String;)V {Person();descriptor: ()Vflags:Code:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: getstatic #3 // Field name:Ljava/lang/String;6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V9: returnLineNumberTable:line 6: 0line 7: 9static {};descriptor: ()Vflags: ACC_STATICCode:stack=1, locals=0, args_size=00: ldc #5 // String wenxl2: putstatic #3 // Field name:Ljava/lang/String;5: returnLineNumberTable:line 3: 0 } SourceFile: "Person.java"?
總結
以上是生活随笔為你收集整理的JVM实战与原理---Class文件结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL中concat函数(连接字符串
- 下一篇: Java数据库篇7——数据库设计