这篇文章完整分析了一个Class字节码文件。只要你认真阅读了每一个字,你就基本掌握了Class字节码。
首先简单的介绍下我们平时编写的Java文件是通过javac命令编译成字节码文件的,如果你不想自己编译,在Java工程的bin目录下也能找到对应的字节码文件。
下面是源码Person类:
package hilbert;
public class Person {
private String name;
private int number;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
通过javac编译成Person.class,使用UltraEdit打开如下:
UltraEdit是查看二进制文件的好工具,可用于分析Class字节码、图片、视频等。它也是一款不错的文本编辑器。
下面是Java虚拟机规范第四章“Class文件格式”中对Class文件格式的描述(可以先粗略浏览下,后面会逐个分析)
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];
}
上面的Person.class字节码文件是完全符合ClassFile文件格式的,这个完全符合包括内容和顺序都是完全符合的。我们现在就来逐个字节的分析。从Person.class字节码文件中的开始字节(0xCA,对应ClassFile中magic的第一个字节)分析到结束字节(0x1F,对应ClassFile中attribute_info attributes[attributes_count]最后一个字节)。
在分析前,我们先了解下javap -p -verbose (或者javap -p -v,两者是一样的)命令:
注意:javap -v 或者javap -verbose 不能解析出private方法和private字段!!!有private方法或字段的需要加上-p才能解析出来。很多文章或书籍都没提到这个。
C:\Users\Administrator>javap -p -verbose D:\eclipse-workspace\Demo1\bin\hilbert\Per
son.class
C:\Users\Administrator>javap -p -verbose D:\eclipse-workspace\Demo1\bin\hilbert\Per
son.class
产生的结果如下:
C:\Users\Administrator>javap -p -v D:\eclipse-workspace\Demo\bin\hilbert\Person.
class
Classfile /D:/eclipse-workspace/Demo/bin/hilbert/Person.class
Last modified 2020-2-8; size 714 bytes
MD5 checksum a53c8107b7521090395160610ffad96d
Compiled from "Person.java"
public class hilbert.Person
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // hilbert/Person
#2 = Utf8 hilbert/Person
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 name
#6 = Utf8 Ljava/lang/String;
#7 = Utf8 number
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Methodref #3.#13 // java/lang/Object."<init>":()V
#13 = NameAndType #9:#10 // "<init>":()V
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lhilbert/Person;
#18 = Utf8 getName
#19 = Utf8 ()Ljava/lang/String;
#20 = Fieldref #1.#21 // hilbert/Person.name:Ljava/lang/Stri
ng;
#21 = NameAndType #5:#6 // name:Ljava/lang/String;
#22 = Utf8 setName
#23 = Utf8 (Ljava/lang/String;)V
#24 = Utf8 getNumber
#25 = Utf8 ()I
#26 = Fieldref #1.#27 // hilbert/Person.number:I
#27 = NameAndType #7:#8 // number:I
#28 = Utf8 setNumber
#29 = Utf8 (I)V
#30 = Utf8 SourceFile
#31 = Utf8 Person.java
{
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
private int number;
descriptor: I
flags: ACC_PRIVATE
public hilbert.Person();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #12 // Method java/lang/Object."<init>
":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lhilbert/Person;
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #20 // Field name:Ljava/lang/String;
4: areturn
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lhilbert/Person;
public void setName(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #20 // Field name:Ljava/lang/String;
5: return
LineNumberTable:
line 12: 0
line 13: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lhilbert/Person;
0 6 1 name Ljava/lang/String;
public int getNumber();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #26 // Field number:I
4: ireturn
LineNumberTable:
line 16: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lhilbert/Person;
public void setNumber(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #26 // Field number:I
5: return
LineNumberTable:
line 20: 0
line 21: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lhilbert/Person;
0 6 1 number I
}
SourceFile: "Person.java"
javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息(粗斜体字不理解不要慌,后面会逐个解释)。注意:这种解析结果是方便人们阅读汇编指令的,而并非给虚拟机装载、执行的。虚拟机真正装载、执行的是上面的Person.class字节码。因此它(javap -v 反解析结果)的顺序并不完全和ClassFile字节码格式吻合,理解这点很重要。 当你完全读完了这篇文章,理解了每一个字,我相信你也能用java代码写出javap 命令,反解析字节码。
下图展示了 Person.java、Person.class、ClassFile文件格式、JVM、javap -v 反解析Person.class结果的关系:
Person.java、Person.class、JVM、ClassFile、反解析Person.class关系.png
接下来按照ClassFile文件格式逐个字节分析Person.class
1、u4 magic
u4代表4个字节,后面的u2代表2个字节。magic 魔数 识别二进制文件格式, Class文件前4个字节表示为:0xCAFEBABE (这个是固定的,它就代表这个文件是Class文件)
class文件魔数.png
JPG图片魔数是前面3个字节 0xFFD8FF
jpg图片魔数.png
2、u2 minor_version
minor_verson 2个字节表示Class文件的小版本信息 这里是0x0000
小版本(minor_version).png
3、u2 major_version
major_version 2个字节表示Class文件版本的大版本信息 这里是 0x0034 转化为10进制 3*16+4=52
大版本(major_version).png JDK版本和major_version对应关系.png
JDK1.8对应的major_version是52。
4、u2 constant_pool_count
constant_pool_count表示常量池常量的数量。这里是0x0020 转化为十进制是16*2=32。代表有32个常量。但是通过刚才javap -v 反编译Persion.class可知常量池中一共有31个常量,而且第一个常量的index是1。其实可以理解有32个常量index为0的代表不引用任何常量。
常量池中常量数量.png
5、cp_info constant_pool[constant_pool_count-1]
cp_info就是刚才常量池中的31个常量。
cp_info格式如下:
cp_info{
u1 tag; //第一个字节表示常量类型
u1 info[]; //后面n个字节表示具体类型常量。
}
常量类型如下表(先粗略的看下,后面每分析一个常量项就会来查这个表,查着查着就熟悉了):
字节码常量类型表.png
根据上面cp_info和观察字节码常量类型表可知:任何类型常量的第一个字节(u1 tag)都表示常量的类型。接下来观察第一个字节 是 0x07 转化为10进制:7。 由字节码常量类型表可知:这是CONSTANT_Class_info常量项,接下来的2个字节表示类的名字在常量池中的索引值: 0x0002 转化为10进制 name_index = 2。
第1个常量:
07 //对应字节码常量类型表 Constant_Class_info
00 02 //name_index 类名索引值 2,对应第2个常量可知,其指向的值为: hilbert/Person
第2个常量
01 //对应字节码常量类型表 Constant_Utf8_info,
00 0E //这两个字节代表字符串的字节长度,00 0E转化为10进制为14。表示接下来14个字节为字符串内容
68 69 6C 62 65 72 74 2F 50 65 72 73 6F 6E//这14个字节就是内容,转化为10进制再对照ASCII码表:hilbert/Person
例如:第一个字符0x68 转化为10进制是104,查ASCII表可知为:h。
ASCII字代码表.jpg
第2个常量项具体内容.png
每分析一个常量,可以回看下上面javap -v 反解析Person.class的结果,可以对照比较是否正确。
接下来的分析会稍微快些:
第3个常量
07//对应字节码常量类型表 Constant_Class_info
00 04//name_index 类名索引值 4,对应第4个常量可知,其指向的值为: java/lang/Object
第4个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 10//这两个字节代表字符串的字节长度,转化为10进制为16。表示接下来16个字节为字符串内容
6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 //这16个字节就是内容,转化为10进制再对照ASCII码表:java/lang/Object
第5个常量
01 //对应字节码常量类型表 Constant_Utf8_info,
00 04//表示接下来4个字节为字符串内容
6E 61 6D 65//name
第6个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 12//表示接下来18个字节为字符串内容
4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B//Ljava/lang/String;
第7个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 06//表示接下来6个字节为字符串内容
6E 75 6D 62 65 72 //number
第8个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 01//表示接下来1个字节为字符串内容
49//I
第9个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 06//表示接下来6个字节为字符串内容
3C 69 6E 69 74 3E //<init>
第10个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 03//表示接下来3个字节为字符串内容
28 29 56 //()V
第11个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 04//表示接下来4个字节为字符串内容
43 6F 64 65//Code
第12个常量
0A//转化为10进制为10。对应字节码常量类型表 Constant_Methodref_info,
00 03//class_index 类名索引值 3,对应第3个常量可知,再指向第4个常量值: java/lang/Object
00 0D//name_and_type_index 表示方法的名字,参数类型返回值类型,指向第13个常量值
第13个常量
0C//转化为10进制为12,对应字节码常量类型表 Constant_NameAndType_info,
00 09//name_index 方法名字,指向第9个常量<init>
00 0A//descriptor_index 描述(参数类型,返回值类型),指向第10个常量()V,表示参数为空,返回值为void
第14个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 0F//表示接下来15个字节为字符串内容
4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 //LineNumberTable
第15个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 12//表示接下来18个字节为字符串内容
4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65//LocalVariableTable
第16个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 04//表示接下来4个字节为字符串内容
74 68 69 73//this
第17个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 10//表示接下来16个字节为字符串内容
4C 68 69 6C 62 65 72 74 2F 50 65 72 73 6F 6E 3B//Lhilbert/Person;
第18个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 07//表示接下来7个字节为字符串内容
67 65 74 4E 61 6D 65 //getName
第19个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 14//表示接下来20个字节为字符串内容
28 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 38 //()Ljava/lang/String;
第20个常量
09//对应字节码常量类型表 Constant_Fieldref_info,
00 01//class_index 表示类名,指向第1个常量,再指向第二个常量值,即hilbert/Person
00 15//name_and_type_index 表示字段名和字段类型,指向第21个常量值,再指向第5第6个常量值,即name:Ljava/lang/String;
由Person.java可知,其中字段name的类型是String,对应字节码类型为"L"+全限定类名+";",其中全限定类名中的"."改成"/",即"Ljava/lang/String;"。所有引用类型的字节码类型都是这样表示:"L"+全限定类名+";"
基本类型和引用类型在字节码中的对应表示:
BaseType Character Type Interpretation
B byte signed byte
C char Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
L ClassName ; reference an instance of class ClassName
S short signed short
Z boolean true or false
[ reference one array dimension
[[ reference double array dimension
第21个常量
0C//转化为10进制为:12 对应字节码常量类型表 Constant_NameAndType_info,
00 05//name_index 字段名,指向第5个常量,即:name
00 06//descriptor_index 描述 字段类型 指向第6个常量,即:Ljava/lang/String;
第22个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 07//表示接下来7个字节为字符串内容
73 65 74 4E 61 6D 65 //setName
第23个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 15//表示接下来21个字节为字符串内容
28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56// (Ljava/lang/String;)V
第24个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 09//表示接下来9个字节为字符串内容
67 65 74 4E 75 6D 62 65 72 // getNumber
第25个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 03//表示接下来3个字节为字符串内容
28 29 49////()I
第26个常量
09//对应字节码常量类型表 Constant_Fieldref_info,
00 01//class_index 表示类名,指向第1个常量,再指向第二个常量值,即hilbert/Person
00 1B////name_and_type_index 表示字段名和字段类型,指向第27个常量值,再指向第7第8个常量值,即name:I;
第27个常量
0C//转化为10进制为:12 对应字节码常量类型表 Constant_NameAndType_info,
00 07//name_index 字段名,指向第7个常量,即:number
00 08//descriptor_index 描述 字段类型 指向第8个常量,即:I
第28个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 09//表示接下来9个字节为字符串内容
73 65 74 4E 75 6D 62 65 72 //setNumber
第29个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 04//表示接下来4个字节为字符串内容
28 49 29 56//(I)V
第30个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 0A//表示接下来10个字节为字符串内容
53 6F 75 72 63 65 46 69 6C 65//SourceFile
第31个常量
01//对应字节码常量类型表 Constant_Utf8_info,
00 0B//表示接下来11个字节为字符串内容
50 65 72 73 6F 6E 2E 6A 61 76 61//Person.java
到此,常量全部解析完毕。
6、u2 access_flags
常量池后,紧接着的2个字节表示类的访问控制信息(access_flags)。
标志名 取值 说明
ACC_PUBLIC 0x0001 public类型.
ACC_FINAL 0x0010 final类型.
ACC_SUPER 0x0020 用于invokespecial指令
ACC_INTERFACE 0x0200 表明这个类是一个Interface
ACC_ABSTRACT 0x0400 abstract类型
ACC_SYNTHETIC 0x1000 表明该类由编译器根据情况生成的,源码里无法显示定义这样的类
ACC_ANNOTATION 0x2000 注解类型
ACC_ENUM 0x4000 枚举类型
参考上表,对应咋们的Person.class
00 21// 0x0021 = 0x0001|0x0020,即表示类是public类型;执行父类方法,无需动态绑定,指令是:invokespecial(可以自行了解invokespecial和invokevirtual的区别)
7、u2 this_class
00 01//指向常量池中第1个常量项,再转向第2个常量项,即:hilbert/Person
8、u2 super_class
00 03//指向常量池中第3个常量项,再转向第4个常量项,即:java/lang/Object
9、u2 interfaces_count
00 00//接口数量,表示有0个接口,即没有接口
10、u2 interfaces[interfaces_count]
由于没有接口,所以此项没有数据,没有字节。
11、u2 fields_count
00 02//表示有2个字段
12、field_info fields[fields_count]
这项就是2个字段的具体内容
field_info的格式如下:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
第1个字段
u2 access_flags字段访问控制
由如下表:
标志名 取值 说明
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 枚举类型.
可知:
00 02//表示此字段是 private类型 即:ACC_PRIVATE
u2 name_index 表示字段名字在常量池中的索引
00 05//指向第5个常量,即:name
u2 descriptor_index 字段描述符索引(字段类型)
00 06//指向第6个常量,即:Ljava/lang/String;
u2 attributes_count 字段属性数量
00 00//表示没有字段属性
attribute_info attributes[attributes_count] 字段中的属性信息
由于此字段没有属性信息,直接到第二个字段。
第2个字段
00 02//表示此字段是 private类型 即:ACC_PRIVATE
00 07//指向第7常量,即:number
00 08//指向第8个常量,即:I;
00 00//表示没有字段属性信息
13、u2 methods_count
00 05//表示有5个方法
14、method_info methods[methods_count]
这项就是2个方法的具体内容
method_info的格式和field_info的格式是完全一致的,格式如下:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中方法的access_flags和字段的access_flags是不同,如下:
标志名 取值 说明
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类型(strict float point,精确浮点).
ACC_SYNTHETIC 0x1000 synthetic类型,在源代码中不存在.
第1个方法
00 01//access_flags public类型
00 09//name_index 方法名字索引,指向第9个常量,即<init>,<init>是构造方法的名字
00 0A//descriptor_index 方法描述索引,即方法的参数和返回值类型,指向第10个常量,即()V。括号()内表示参数类型,此时括号里没有内容,代表没有参数。括号()后面V表示返回值,V表示void,即无返回值。
00 01//属性数量,表示有一个属性
//attribute_info 见下
attribute_info格式
属性不仅可以在方法里有,字段里有可以有(只是上面Person.class的2个字段里没有属性而已),类里也会有,属性内部也会有属性。下面是属性的基本结构:
attribute_info {
u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
u4 attribute_length;//该属性具体内容的长度,即下面info的长度
u1 info[attribute_length];//属性具体类容
}
不同的属性名称,对应不同的属性类型。下面列举5中常见的属性。想查看全部类型属性,可参考:[Java虚拟机规范第四章“Class文件格式”中属性格式]。(https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7)
下面5个属性格式,可以先粗略看下,后面会结合Person.class具体分析。
ConstantValue属性
该属性只出现于field_info中,用于描述一个常量成员域的(long、float、doule、int、short、char、byte、boolean、String等)值。其格式如下:
ConstantValue_attribute {
u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
u4 attribute_length;//该属性具体内容的长度,此属性长度固定是2个字节,长度计算从下一个字节开始。
u2 constantvalue_index;//属性值,指向常量池中常量项的索引
}
Code属性(非常重要)
该属性是非常重要的一个属性,只出现于method_info中,用于描述一个方法(非native和abstract方法)的内容:即源码中该方法内容编译后得到的最大栈深度,最大局部变量数、虚拟机指令、包含的其他属性等。其结构如下:
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;一个方法中try/catch语句的数量
{ u2 start_pc;//try/catch语句从那条指令开始的
u2 end_pc;//try/catch语句到哪条指令结束。注意,只包括try语句,不包括catch
u2 handler_pc;//表示catch语句的内容从哪条指令开始。
u2 catch_type;//表示catch中截获的Exception或Error的名字,指向Utf8_info常量项。如果catch_type取值为0,则表示它是final{}语句块。
} exception_table[exception_table_length];//每个try/catch语句的内容
u2 attributes_count;//Code_attribute内部包含的其他属性数量
attribute_info attributes[attributes_count];//Code_attribute内部包含的其他属性内容
}
Code属性中包含虚拟机指令(u1 code[code_length])下表是具体的虚拟机指令集:
字节码 助记符 指令含义
0x00 nop 什么都不做
0x01 aconst_null 将null推送至栈顶
0x02 iconst_m1 将int型-1推送至栈顶
0x03 iconst_0 将int型0推送至栈顶
0x04 iconst_1 将int型1推送至栈顶
0x05 iconst_2 将int型2推送至栈顶
0x06 iconst_3 将int型3推送至栈顶
0x07 iconst_4 将int型4推送至栈顶
0x08 iconst_5 将int型5推送至栈顶
0x09 lconst_0 将long型0推送至栈顶
0x0a lconst_1 将long型1推送至栈顶
0x0b fconst_0 将float型0推送至栈顶
0x0c fconst_1 将float型1推送至栈顶
0x0d fconst_2 将float型2推送至栈顶
0x0e dconst_0 将double型0推送至栈顶
0x0f dconst_1 将double型1推送至栈顶
0x10 bipush 将单字节的常量值(-128~127)推送至栈顶
0x11 sipush 将一个短整型常量值(-32768~32767)推送至栈顶
0x12 ldc 将int, float或String型常量值从常量池中推送至栈顶
0x13 ldc_w 将int, float或String型常量值从常量池中推送至栈顶(宽索引)
0x14 ldc2_w 将long或double型常量值从常量池中推送至栈顶(宽索引)
0x15 iload 将指定的int型本地变量
0x16 lload 将指定的long型本地变量
0x17 fload 将指定的float型本地变量
0x18 dload 将指定的double型本地变量
0x19 aload 将指定的引用类型本地变量
0x1a iload_0 将第一个int型本地变量
0x1b iload_1 将第二个int型本地变量
0x1c iload_2 将第三个int型本地变量
0x1d iload_3 将第四个int型本地变量
0x1e lload_0 将第一个long型本地变量
0x1f lload_1 将第二个long型本地变量
0x20 lload_2 将第三个long型本地变量
0x21 lload_3 将第四个long型本地变量
0x22 fload_0 将第一个float型本地变量
0x23 fload_1 将第二个float型本地变量
0x24 fload_2 将第三个float型本地变量
0x25 fload_3 将第四个float型本地变量
0x26 dload_0 将第一个double型本地变量
0x27 dload_1 将第二个double型本地变量
0x28 dload_2 将第三个double型本地变量
0x29 dload_3 将第四个do le型本地变量
0x2a aload_0 将第一个引用类型本地变量
0x2b aload_1 将第二个引用类型本地变量
0x2c aload_2 将第三个引用类型本地变量
0x2d aload_3 将第四个引用类型本地变量
0x2e iaload 将int型数组指定索引的值推送至栈顶
0x2f laload 将long型数组指定索引的值推送至栈顶
0x30 faload 将float型数组指定索引的值推送至栈顶
0x31 daload 将double型数组指定索引的值推送至栈顶
0x32 aaload 将引用型数组指定索引的值推送至栈顶
0x33 baload 将boolean或byte型数组指定索引的值推送至栈顶
0x34 caload 将char型数组指定索引的值推送至栈顶
0x35 saload 将short型数组指定索引的值推送至栈顶
0x36 istore 将栈顶int型数值存入指定本地变量
0x37 lstore 将栈顶long型数值存入指定本地变量
0x38 fstore 将栈顶float型数值存入指定本地变量
0x39 dstore 将栈顶double型数值存入指定本地变量
0x3a astore 将栈顶引用型数值存入指定本地变量
0x3b istore_0 将栈顶int型数值存入第一个本地变量
0x3c istore_1 将栈顶int型数值存入第二个本地变量
0x3d istore_2 将栈顶int型数值存入第三个本地变量
0x3e istore_3 将栈顶int型数值存入第四个本地变量
0x3f lstore_0 将栈顶long型数值存入第一个本地变量
0x40 lstore_1 将栈顶long型数值存入第二个本地变量
0x41 lstore_2 将栈顶long型数值存入第三个本地变量
0x42 lstore_3 将栈顶long型数值存入第四个本地变量
0x43 fstore_0 将栈顶float型数值存入第一个本地变量
0x44 fstore_1 将栈顶float型数值存入第二个本地变量
0x45 fstore_2 将栈顶float型数值存入第三个本地变量
0x46 fstore_3 将栈顶float型数值存入第四个本地变量
0x47 dstore_0 将栈顶double型数值存入第一个本地变量
0x48 dstore_1 将栈顶double型数值存入第二个本地变量
0x49 dstore_2 将栈顶double型数值存入第三个本地变量
0x4a dstore_3 将栈顶double型数值存入第四个本地变量
0x4b astore_0 将栈顶引用型数值存入第一个本地变量
0x4c astore_1 将栈顶引用型数值存入第二个本地变量
0x4d astore_2 将栈顶引用型数值存入第三个本地变量
0x4e astore_3 将栈顶引用型数值存入第四个本地变量
0x4f iastore 将栈顶int型数值存入指定数组的指定索引位置
0x50 lastore 将栈顶long型数值存入指定数组的指定索引位置
0x51 fastore 将栈顶float型数值存入指定数组的指定索引位置
0x52 dastore 将栈顶double型数值存入指定数组的指定索引位置
0x53 aastore 将栈顶引用型数值存入指定数组的指定索引位置
0x54 bastore 将栈顶boolean或byte型数值存入指定数组的指定索引位置
0x55 castore 将栈顶char型数值存入指定数组的指定索引位置
0x56 sastore 将栈顶short型数值存入指定数组的指定索引位置
0x57 pop 将栈顶数值弹出 (数值不能是long或double类型的)
0x58 pop2 将栈顶的一个(long或double类型的)或两个数值弹出(其它)
0x59 dup 复制栈顶数值并将复制值压入栈顶
0x5a dup_x1 复制栈顶数值并将两个复制值压入栈顶
0x5b dup_x2 复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5c dup2 复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶
0x5d dup2_x1 dup_x1 指令的双倍版本
0x5e dup2_x2 dup_x2 指令的双倍版本
0x5f swap 将栈最顶端的两个数值互换(数值不能是long或double类型的)
0x60 iadd 将栈顶两int型数值相加并将结果压入栈顶
0x61 ladd 将栈顶两long型数值相加并将结果压入栈顶
0x62 fadd 将栈顶两float型数值相加并将结果压入栈顶
0x63 dadd 将栈顶两double型数值相加并将结果压入栈顶
0x64 is 将栈顶两int型数值相减并将结果压入栈顶
0x65 ls 将栈顶两long型数值相减并将结果压入栈顶
0x66 fs 将栈顶两float型数值相减并将结果压入栈顶
0x67 ds 将栈顶两double型数值相减并将结果压入栈顶
0x68 imul 将栈顶两int型数值相乘并将结果压入栈顶
0x69 lmul 将栈顶两long型数值相乘并将结果压入栈顶
0x6a fmul 将栈顶两float型数值相乘并将结果压入栈顶
0x6b dmul 将栈顶两double型数值相乘并将结果压入栈顶
0x6c idiv 将栈顶两int型数值相除并将结果压入栈顶
0x6d ldiv 将栈顶两long型数值相除并将结果压入栈顶
0x6e fdiv 将栈顶两float型数值相除并将结果压入栈顶
0x6f ddiv 将栈顶两double型数值相除并将结果压入栈顶
0x70 irem 将栈顶两int型数值作取模运算并将结果压入栈顶
0x71 lrem 将栈顶两long型数值作取模运算并将结果压入栈顶
0x72 frem 将栈顶两float型数值作取模运算并将结果压入栈顶
0x73 drem 将栈顶两double型数值作取模运算并将结果压入栈顶
0x74 ineg 将栈顶int型数值取负并将结果压入栈顶
0x75 lneg 将栈顶long型数值取负并将结果压入栈顶
0x76 fneg 将栈顶float型数值取负并将结果压入栈顶
0x77 dneg 将栈顶double型数值取负并将结果压入栈顶
0x78 ishl 将int型数值左移位指定位数并将结果压入栈顶
0x79 lshl 将long型数值左移位指定位数并将结果压入栈顶
0x7a ishr 将int型数值右(符号)移位指定位数并将结果压入栈顶
0x7b lshr 将long型数值右(符号)移位指定位数并将结果压入栈顶
0x7c iushr 将int型数值右(无符号)移位指定位数并将结果压入栈顶
0x7d lushr 将long型数值右(无符号)移位指定位数并将结果压入栈顶
0x7e iand 将栈顶两int型数值作“按位与”并将结果压入栈顶
0x7f land 将栈顶两long型数值作“按位与”并将结果压入栈顶
0x80 ior 将栈顶两int型数值作“按位或”并将结果压入栈顶
0x81 lor 将栈顶两long型数值作“按位或”并将结果压入栈顶
0x82 ixor 将栈顶两int型数值作“按位异或”并将结果压入栈顶
0x83 lxor 将栈顶两long型数值作“按位异或”并将结果压入栈顶
0x84 iinc 将指定int型变量增加指定值(i++, i–, i+=2)
0x85 i2l 将栈顶int型数值强制转换成long型数值并将结果压入栈顶
0x86 i2f 将栈顶int型数值强制转换成float型数值并将结果压入栈顶
0x87 i2d 将栈顶int型数值强制转换成double型数值并将结果压入栈顶
0x88 l2i 将栈顶long型数值强制转换成int型数值并将结果压入栈顶
0x89 l2f 将栈顶long型数值强制转换成float型数值并将结果压入栈顶
0x8a l2d 将栈顶long型数值强制转换成double型数值并将结果压入栈顶
0x8b f2i 将栈顶float型数值强制转换成int型数值并将结果压入栈顶
0x8c f2l 将栈顶float型数值强制转换成long型数值并将结果压入栈顶
0x8d f2d 将栈顶float型数值强制转换成do le型数值并将结果压入栈顶
0x8e d2i 将栈顶double型数值强制转换成int型数值并将结果压入栈顶
0x8f d2l 将栈顶double型数值强制转换成long型数值并将结果压入栈顶
0x90 d2f 将栈顶double型数值强制转换成float型数值并将结果压入栈顶
0x91 i2b 将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
0x92 i2c 将栈顶int型数值强制转换成char型数值并将结果压入栈顶
0x93 i2s 将栈顶int型数值强制转换成short型数值并将结果压入栈顶
0x94 lcmp 比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
0x95 fcmpl 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x96 fcmpg 比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x97 dcmpl 比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
0x98 dcmpg 比较栈顶两do le型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
0x99 ifeq 当栈顶int型数值等于0时跳转
0x9a ifne 当栈顶int型数值不等于0时跳转
0x9b iflt 当栈顶int型数值小于0时跳转
0x9c ifge 当栈顶int型数值大于等于0时跳转
0x9d ifgt 当栈顶int型数值大于0时跳转
0x9e ifle 当栈顶int型数值小于等于0时跳转
0x9f if_icmpeq 比较栈顶两int型数值大小,当结果等于0时跳转
0xa0 if_icmpne 比较栈顶两int型数值大小,当结果不等于0时跳转
0xa1 if_icmplt 比较栈顶两int型数值大小,当结果小于0时跳转
0xa2 if_icmpge 比较栈顶两int型数值大小,当结果大于等于0时跳转
0xa3 if_icmpgt 比较栈顶两int型数值大小,当结果大于0时跳转
0xa4 if_icmple 比较栈顶两int型数值大小,当结果小于等于0时跳转
0xa5 if_acmpeq 比较栈顶两引用型数值,当结果相等时跳转
0xa6 if_acmpne 比较栈顶两引用型数值,当结果不相等时跳转
0xa7 goto 无条件跳转
0xa8 jsr 跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
0xa9 ret 返回至本地变量
0xaa tableswitch 用于switch条件跳转,case值连续(可变长度指令)
0xab lookupswitch 用于switch条件跳转,case值不连续(可变长度指令)
0xac ireturn 从当前方法返回int
0xad lreturn 从当前方法返回long
0xae freturn 从当前方法返回float
0xaf dreturn 从当前方法返回double
0xb0 areturn 从当前方法返回对象引用
0xb1 return 从当前方法返回void
0xb2 getstatic 获取指定类的静态域,并将其值压入栈顶
0xb3 putstatic 为指定的类的静态域赋值
0xb4 getfield 获取指定类的实例域,并将其值压入栈顶
0xb5 putfield 为指定的类的实例域赋值
0xb6 invokevirtual 调用实例方法
0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法
0xb8 invokestatic 调用静态方法
0xb9 invokeinterface 调用接口方法
0xba – 无此指令
0xbb new 创建一个对象,并将其引用值压入栈顶
0xbc newarray 创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
0xbd anewarray 创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
0xbe arraylength 获得数组的长度值并压入栈顶
0xbf athrow 将栈顶的异常抛出
0xc0 checkcast 检验类型转换,检验未通过将抛出ClassCastException
0xc1 instanceof 检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
0xc2 monitorenter 获得对象的锁,用于同步方法或同步块
0xc3 monitorexit 释放对象的锁,用于同步方法或同步块
0xc4 wide 使用附加字节扩展局部变量索引(iinc指令特殊)
0xc5 multianewarray 创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶
0xc6 ifnull 为null时跳转
0xc7 ifnonnull 不为null时跳转
0xc8 goto_w 无条件跳转(宽索引)
0xc9 jsr_w 跳转至指定32位offset位置,并将jsr_w下一条指令地址压入栈顶
Exceptions_attribute
当一个方法抛出异常(Exception)或错误(Error)时,这个方法的method_info将保存此属性
Exceptions_attribute {
u2 attribute_name_index;//属性名字在常量池中的索引
u4 attribute_length;//属性字节长度,从下一个字节开始算
u2 number_of_exceptions;//异常数量
u2 exception_index_table[number_of_exceptions];//每个异常在常量池中的索引值
}
SourceFile_attribute
此属性属于类属性,就是ClassFile文件格式中的最后一项。表示Class对应的源码文件名。是指向常量池的一个索引值。
SourceFile_attribute {
u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
u4 attribute_length;//该属性具体内容的长度,此属性长度固定是2个字节,长度计算从下一个字节开始。
u2 sourcefile_index;//表示Class对应的源码文件名
}
LocalVariableTable_attribute
这个属性是包含在Code里的属性,用来描述一个方法的局部变量相关信息。比如变量作用域(start,length)、位置(slot)、名字(Name)、类型(Signature)
LocalVariableTable_attribute {
u2 attribute_name_index;//属性名字在常量池中的索引
u4 attribute_length;//属性字节长度,从下一个字节开始算
u2 local_variable_table_length;//局部变量数量
{ u2 start_pc;//局部变量作用的开始指令位置
u2 length;//局部变量作用域的长度
u2 name_index;//局部变量名字,指向常量池的索引
u2 descriptor_index;//局部变量描述(类型),指向常量池的索引
u2 index;//局部变量在局部变量表中的位置
} local_variable_table[local_variable_table_length];//每个局部变量
}
LineNumberTable_attribute
这个属性是包含在Code里的属性,表明字节码指令和和源码的对应关系。即指令开始位置(start_pc)对应源码行号(line_number)。不一定是一一对应,可能多对一,即多条指令对应一行源码。用于Java调试。
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];//每条对应关系
}
上面介绍了5中常见的属性。
我们接着分析Person.class。
由上面的属性格式可知,所有属性前2个字节表示属性的名字在常量池中的索引。然后接下来的4个字节表示属性内容的长度。Person.class中如下:
第一个方法(构造方法)的Code属性.png 第一个方法javap -v反解析内容的Code属性.png
00 0B//属性名字,转为10进制是:11,指向常量池中第11个常量,即 Code。表明是一个Code属性
00 00 00 2F//属性内容有多少字节,2/*16+15=47。表示接下来47个字节描述Code属性信息。
通过上面的分析,发现是一个Code属性,且其内容有47个字节。那我们按照Code属性的格式分析接下来的47个字节:
00 01//操作数栈深度的最大值为1
00 01//局部变量表所需的存储空间为1个Slot;max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位,一般来讲就是1byte。
00 00 00 05//方法指令所需要的字节数。并不代表有5条指令。它包含指令和指令的参数。本例中就只有3条指令,其中一条指令(invokespecial) 有一个参数 这个参数是2个字节。加起来就是5个字节。
2A//aload_0 将第一个引用类型局部变量推送至栈顶 根据后面的LocalVariableTable可知:第一个引用类型局部变量是this
B7//invokespecial 调用超类构造方法,实例初始化方法,私有方法
00 0C//是invokespecial 后的参数 指向常量池,转化为10进制是12 即:第12个常量,查下常量池可得:12->Methodref ->3,13-> java/lang/Object."<init>":()V 。表示执行父类构造方法构造方法
B1//return 从当前方法返回void
00 00 //exception_table_length 表示没有异常
00 02 //attributes_count 表示Code属性里还有2个内部属性。接下来就是2个内部属性的具体内容
00 0E//第一个内部属性的名字,指向常量池的索引 转化为10进制是:14 ,即第14个常量:LineNumberTable
00 00 00 06////属性字节长度为6,从下一个字节开始算
00 01//其行号表长度为1,即有1个行号表
00 00//start_pc 字节码行号为0,即 aload_0 代表加载this
00 03//line_number源码行号为3.即 public class Person{
00 0F//表示第二个内部属性的名字,转化为10进制是15,即第15个常量:LocalVariableTable 局部变量表属性
00 00 00 0C//属性字节长度为12,从下一个字节开始算
00 01//表示有一个局部变量
00 00//start_pc 表示局部变量开始作用的字节码行号是0号,即aload_0
00 05//length,表示这个局部变量的作用域长度是5
00 10//name_index,局部变量名字,指向常量池的索引,转为10进制是16,即第16个常量:this
00 11//descriptor_index,局部变量的描述(类型描述),指向常量池的索引,转为10进制是17,即第17个常量:Lhilbert/Person
00 00//index,局部变量this在局部变量表中位置。即第0个位置。
到此第一个方法分析完毕!
第2个方法
Person.class如下:
第二个方法-getName().png
第二个方法.png
00 01//access_flags=ACC_PUBLIC 表示public类型方法
00 12//name_index,方法名字索引,转化为10进制是:18,指向常量池为:getName
00 13//descriptor_index,方法描述(参数和返回值)索引,转化为10进制是:19,指向常量池为:()Ljava/lang/String; 表示没有参数,返回值类型是String。
00 01//方法属性数量为1
00 0B//第一个方法属性的名字的在常量池中的索引,0B->11->Code。表示是个Code属性
00 00 00 2F//表示Code属性内容的长度为2*16+15=47个字节
00 01//操作数栈深度的最大值为1
00 01//局部变量表所需的存储空间为1个Slot;max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位,一般来讲就是1byte。
00 00 00 05//指令内容所占字节数为5个字节
2A//aload_0 将第一个引用类型局部变量推送至栈顶 根据后面的LocalVariableTable可知:第一个引用类型局部变量是this
B4//getfield 获取指定类的实例域,并将其值压入栈顶,下面2个字节是它的参数
00 14// 转化为10进制是20,即:20->Fieldref->#1.#21->hilbert/Person.name:Ljava/lang/Stri
ng;(对应源码即name字段,加上上面的aload_0 即是this.name)
B0//areturn 从当前方法返回对象引用 即返回对应源码:return this.name;
00 00//表示没有异常
00 02//表示有2个内部属性
00 0E//第一个内部属性的名字,指向常量池的索引 转化为10进制是:14 ,即第14个常量:LineNumberTable
00 00 00 06//表示接下来6个字节为LineNumberTable属性的内容
00 01//其行号表长度为1,即有1个行号表
00 00//start_pc 字节码行号为0,即 aload_0 代表加载this
00 09//line_number源码行号为9.即 return name;
00 0F//表示第2个内部属性的名字,转化为10进制是15,即第15个常量:LocalVariableTable 局部变量表属性
00 00 00 0C//属性字节长度为12,从下一个字节开始算
00 01//表示有一个局部变量
00 00//start_pc 表示局部变量开始作用的字节码行号是0号,即aload_0
00 05//length,表示这个局部变量的作用域长度是5
00 10//name_index,局部变量名字,指向常量池的索引,转为10进制是16,即第16个常量:this
00 11//descriptor_index,局部变量的描述(类型描述),指向常量池的索引,转为10进制是17,即第17个常量:Lhilbert/Person
00 00//index,局部变量this在局部变量表中位置。即第0个位置。
第三个方法
Person.class如下:
第三个方法-setName(String name).png 第三个方法.png
00 01//access_flags=ACC_PUBLIC 表示public类型方法
00 16//name_index,方法名字索引,转化为10进制是:22,指向常量池为:setName
00 17//descriptor_index,方法描述(参数和返回值)索引,转化为10进制是:23,指向常量池为:(Ljava/lang/String;)V; 表示1个参数,类型为String,返回值类类Void。
00 01//方法属性数量为1
00 0B//第一个方法属性的名字的在常量池中的索引,0B->11->Code。表示是个Code属性
00 00 00 3E//3*16+14=62,表示接下来62个字节是Code属性的内容
00 02//操作数栈深度的最大值为2
00 02//局部变量表所需的存储空间为2个Slot;max_locals的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位,一般来讲就是1byte。
00 00 00 06//指令内容所占字节数为6个字节
2A//aload_0 将第一个引用类型局部变量推送至栈顶 根据后面的LocalVariableTable可知:第一个引用类型局部变量是this
2B//aload_1 将第二个引用类型本地变量推送至栈顶 即:name
B5//putfield 为指定的类的实例域赋值 即为name赋值
00 14// 赋值参数 0x0014->#20->Fieldref->#1.#21->hilbert/Person.name:Ljava/lang/String;
B1//return 从当前方法返回void
00 00//表示没有异常
00 02//表示有2个内部属性
00 0E//第一个内部属性的名字,指向常量池的索引 转化为10进制是:14 ,即第14个常量:LineNumberTable
00 00 00 0A//表示接下来10个字节为LineNumberTable属性的内容
00 02//其行号表长度为2,即有2个行号表
00 00//第一个行号的start_pc 字节码行号为0,即 aload_0 代表加载this
00 0D//line_number源码行号为13.即 "this.name = name";
00 05//第二个行号的start_pc 字节码行号为5,即 return 代表方法返回void
00 0E//line_number源码行号为14。即"}";
00 0F//表示第2个内部属性的名字,转化为10进制是15,即第15个常量:LocalVariableTable 局部变量表属性
00 00 00 16//属性字节长度为22,从下一个字节开始算
00 02//表示有2个局部变量
00 00//第1个局部变量(this)的start_pc, 表示局部变量开始作用的字节码行号是0号,即aload_0
00 06//length,表示这个局部变量的作用域长度是6
00 10//name_index,局部变量名字,指向常量池的索引,转为10进制是16,即第16个常量:this
00 11//descriptor_index,局部变量的描述(类型描述),指向常量池的索引,转为10进制是17,即第17个常量:Lhilbert/Person
00 00//index,第1个局部变量this在局部变量表中位置。即第0个位置。
00 00//第2个局部变量(name)的start_pc ,表示局部变量开始作用的字节码行号是0号,即aload_0
00 06//length,表示这个局部变量的作用域长度是6
00 05//name_index,局部变量名字,指向常量池的索引,即:name
00 06//descriptor_index,局部变量的描述(类型描述),指向常量池的索引,即:Ljava/lang/String;
00 01//index,第2个局部变量name在局部变量表中位置。即第1个位置。
第4,第5个方法
第4,第5个方法和第2,第3个方法非常相似,就不具体分析了。
下面是它们的Person.class
第4个方法-getNumber().png
第4个方法.png
第5个方法-setNumber(int number).png
第5个方法.png
到此,所有方法分析完毕!
15、u2 attributes_count
attributes_count 类的属性数量
00 01//表示有1个类属性
16、attribute_info attributes[attributes_count]
attribute_info 具体的类属性
这个是个SourceFile属性,上面有介绍,在这儿再写一遍它的格式:
SourceFile_attribute {
u2 attribute_name_index;//属性名字,指向常量池中常量项的索引
u4 attribute_length;//该属性具体内容的长度,此属性长度固定是2个字节,长度计算从下一个字节开始。
u2 sourcefile_index;//表示Class对应的源码文件名
}
Person.class:
00 1E//属性名字索引,0x1E->#30->SourceFile,
00 00 00 02////该属性具体内容的长度,此属性长度固定是2个字节,长度计算从下一个字节开始。
00 1F//属性内容索引,0x1F->#31->Person.java,即是。源代码文件名
到此,一个完整的Class文件分析完毕!
网友评论