public class MyTest1 {
private int a=1;
public MyTest1() {
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
使用WinHex打开这个类的字节码文件:MyTest1.class
0.java字节码整体结构
类型 | 名称 | 说明 | 长度 |
---|---|---|---|
u4 | magic | 魔数,识别Class文件格式 | 4个字节 |
u2 | minor_version | 次版本号 | 2个字节 |
u2 | major_version | 主版本号 | 2个字节 |
u2 | constant_pool_count | 常量池个数 | 2个字节 |
cp_info | constant_pool | 常量池表 | n个字节 |
u2 | access_flags | 访问标志 | 2个字节 |
u2 | this_class | 类索引 | 2个字节 |
u2 | super_class | 父类索引 | 2个字节 |
u2 | interfaces_count | 接口计数器 | 2个字节 |
u2 | interfaces | 接口索引集合 | 2个字节 |
u2 | fields_count | 字段个数 | 2个字节 |
field_info | fields | 字段集合 | n个字节 |
u2 | methods_count | 方法计数器 | 2个字节 |
method_info | methods | 方法集合 | n个字节 |
u2 | attributes_count | 附加属性计数器 | 2个字节 |
attribute_info | attributes | 附加属性集合 | n个字节 |
1.魔数
所有的.class
字节码文件的前4个字节都是魔数,魔数值为固定值:0xCAFEBABE
2.版本信息
魔数之后的4个字节为,前两个字节表示minor version(次版本),后两个字节表示major version(主版本号)
这里的版本号为00 00 00 34
,换算成十进制,表示次版本号为0,主版本号为52,所以该文件的版本号为:1.8.0
3.常量池
- 紧接着版本号之后的就是常量池入口,一个java类中定义的很多信息都是由常量池来维护和描述的,可以将常量池看作是class文件的资源仓库。比如说:java类中定义的方法与变量信息,都是存储在常量池中。
- 常量池中主要存储两类常量:字面量与符号引用。字面量如文本字符串,java中声明为final的常量值等,而符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符。
4.常量池的总体结构
- Java类所对应的常量池主要由常量池数量和常量池数组这两部分构成,常量池数量紧跟在主版本号后面,占据两个字节;
常量池数组中的元素的个数=常量池数-1(其中0暂时不使用),目的是满足某些常量池索引值的数据在特定情况下需要表达[不引用任何一个常量池]的含义;根本原因在于索引为0也是一个常量(保留常量),只不过它不位于常量表中,这个常量就对应于null值。 - 常量池数组紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同元素的类型,结构都是不同的,长度当然也不同;但是每一种元素的第一个数据都是一个
u1
类型,该字节是标志位,占据1个字节。jvm在解析常量池时,会根据这个u1
类型来获取元素的具体类型。
下面根据使用WinHex软件显示的MyTest1.class在计算机磁盘中保存的16进制数据,反推出MyTest1.class的内容:
常量池中14中常量项的结构总表
class字节码中有两种数据类型:
- 字节数据直接量:这是基本的数据类型,细分为u1,u2,u4,u8四种,分别代表连续的1个字节,2个字节,4个字节,8个字节组成的整体数据。
- 表(数组):表是由多个基本数据或其他表,按照既定顺序组成的大的数据集合,表是有结构的,他的结构体现在:组成表的成分所在的位置和顺序都是已经严格定义好的。(比如常量池项结构表)
常量池中14中常量项的结构总表
常量池数组紧跟在常量池数量之后。常量池数组与一般的数组不同的是,常量池数组中不同元素的类型,结构都是不同的,长度当然也不同;但是每一种元素的第一个数据都是一个u1
类型,该字节是标志位,占据1个字节。
第1个常量
1.先找到常量池数组中元素的标志位u1对应的值:u1=10
2.查表u1=10:
由表可得,u1后面有2个索引项,总共占据4个字节,其中前两个字节代表类描述符的索引项,后两个字节代表类型描述符的索引项。
第2个常量
接着往下走,先找u1标志位对应的值:u1=9
查表u1=9:
u1=9,有两个索引项,占据4个字节,前两个字节代表声明字段的类或者接口描述符的索引项,后两个字节代表字段描述符的索引项。
第3个常量
先找u1标志位对应的值:u1=7
查表的u1=7:
u1=7后面有一个索引项,占据2个字节,代表全限定类名常量项的索引
第4个常量
第5个常量
首先确定u1的值:
根据u1=1进行查表:
由表可得,有两项内容:
一个是length,占据两个字节,代表字符串占用的字节数
一个是bytes,代表长度为length的字符串
第6个变量
第7个变量
第8个变量
第9个变量
第10个变量
第11个变量
第12个变量
第13个变量
第14个变量
第15个变量
第16个变量
第17个变量
第18个变量
第19个变量
第20个变量
第21个变量
第22个变量
第23个变量
第24个变量
第25个变量
第26个变量
第27个变量
总体分析:
根据IDEA带的javap -verbose命令反编译MyTest.class文件:
我们根据二进制数据分析出来的各个常量项和反编译的结果相同
Constant pool:
#1 = Methodref #4.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#25 // com/hh/MyTest1.a:I
#3 = Class #26 // com/hh/MyTest1
#4 = Class #27 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/hh/MyTest1;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 args
#21 = Utf8 [Ljava/lang/String;
#22 = Utf8 SourceFile
#23 = Utf8 MyTest1.java
#24 = NameAndType #7:#8 // "<init>":()V
#25 = NameAndType #5:#6 // a:I
#26 = Utf8 com/hh/MyTest1
#27 = Utf8 java/lang/Object