JVM实战与原理
目录
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 15
00000000 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 12
2.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.class
Last modified 2020-12-19; size 513 bytes
MD5 checksum dea507ac0ecc8c9063c778c6f748a2bb
Compiled from "Person.java"
class Person
minor version: 0
major version: 52
flags: 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: ()V
flags:
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 static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: 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;)V
9: return
LineNumberTable:
line 6: 0
line 7: 9
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #5 // String wenxl
2: putstatic #3 // Field name:Ljava/lang/String;
5: return
LineNumberTable:
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属性表结构:
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];
}
字节码详解: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属性表结构:
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];
}
字节码详解: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.class
Last modified 2020-12-19; size 513 bytes
MD5 checksum dea507ac0ecc8c9063c778c6f748a2bb
Compiled from "Person.java"
class Person
minor version: 0
major version: 52
flags: 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: ()V
flags:
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 static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: 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;)V
9: return
LineNumberTable:
line 6: 0
line 7: 9
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: ldc #5 // String wenxl
2: putstatic #3 // Field name:Ljava/lang/String;
5: return
LineNumberTable:
line 3: 0
}
SourceFile: "Person.java"