.class文件结构表如下图:

魔数

次版本号

主版本号


常量池大小

得到常量池大小18个
可通过命令javap -verbose Demo.class查看得到结果
下面是常量池结果
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#16 // com/neo/asmtest/Bytecode/Demo.age:I
#3 = Class #17 // com/neo/asmtest/Bytecode/Demo
#4 = Class #18 // java/lang/Object
#5 = Utf8 age
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 getAge
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 Demo.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = NameAndType #5:#6 // age:I
#17 = Utf8 com/neo/asmtest/Bytecode/Demo
#18 = Utf8 java/lang/Object
下面开始分析具体的常量需要用到如下常量表



常量池
常量1

CONSTANT_Methodref_info
它的具体结构是:
CONSTANT_Methodref_info{
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
所以这个常量的所有字节码就是

通过结构可知:
tag = 0x0A = 10
class_index = 0x04 = 4 -> 指向第4常量 -> 指向第18常量 -> java/lang/Object
name_and_type_index = 0x0F = 15 -> 指向第15常量 -> <init> ()V
此常量表示:
java/lang/Object."<init>":()V
常量2

CONSTANT_Fieldref_info
它的具体结构是:
CONSTANT_Fieldref_info{
u1 tag;
u2 class_index;
u2 name_and_type_index
}
所以这个常量的所有字节码就是

通过结构可知:
tag = 0x09 = 9
class_index = 0x03 = 3 -> 指向第3常量 -> 指向第17常量 -> com/neo/asmtest/Bytecode/Demo
name_and_type_index = 0x10 = 16 -> 指向第16常量 -> age:I
此常量表示:
com/neo/asmtest/Bytecode/Demo.age:I
常量3

CONSTANT_Class_info
它的具体结构是:
CONSTANT_Class_info{
u1 tag;
u2 name_and_type_index
}
所以这个常量的所有字节码就是

通过结构可知:
tag = 0x07 = 7
name_and_type_index = 0x11 = 17 -> 指向第17常量 -> com/neo/asmtest/Bytecode/Demo
此常量表示:
com/neo/asmtest/Bytecode/Demo
常量4

CONSTANT_Class_info
它的具体结构是:
CONSTANT_Class_info{
u1 tag;
u2 name_and_type_index
}
所以这个常量的所有字节码就是

通过结构可知:
tag = 0x07 = 7
name_and_type_index = 0x12 = 18 ->指向第18常量 -> java/lang/Object
常量5

CONSTANT_utf8_info
它的具体结构是:
CONSTANT_utf8_info{
u1 tag;
u2 length;
length bytes[];
}
所以这个常量的所有字节码就是

通过结构可知:
tag = 0x01 = 1
length = 0x03 = 3 有3个utf8字节
byets = 0x61 0x67 0x65 -> age
常量6

通过结构可知:
tag = 0x01 = 1
length = 0x01 = 1 有1个utf8字节
byets = 0x49 -> I
常量7

通过结构可知:
tag = 0x01 = 1
length = 0x06 = 6 有6个utf8字节
byets = 0x3C 0x69 0x6E 0x69 0x74 0x3E -> <init>
常量8

通过结构可知:
tag = 0x01 = 1
length = 0x03 = 3 有3个utf8字节
byets = 0x3C 0x28 0x29 0x56 -> ()V
常量9

通过结构可知:
tag = 0x01 = 1
length = 0x04 = 4 有4个utf8字节
byets = 0x43 0x6F 0x64 0x65 -> Code
常量10

通过结构可知:
tag = 0x01 = 1
length = 0x0F = 15 有15个utf8字节
byets = 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 -> LineNumberTable
常量11

通过结构可知:
tag = 0x01 = 1
length = 0x06 = 6 有6个utf8字节
byets = 67 65 74 41 67 65 -> getAge
常量12

通过结构可知:
tag = 0x01 = 1
length = 0x03 = 3 有3个utf8字节
byets = 28 29 49 -> ()I
常量13

通过结构可知:
tag = 0x01 = 1
length = 0x0A = 10 有10个utf8字节
byets = 53 6F 75 72 63 65 46 69 6C 65 -> SourceFile
常量14

通过结构可知:
tag = 0x01 = 1
length = 0x09 = 9 有9个utf8字节
byets = 44 65 6D 6F 2E 6A 61 76 61 -> Demo.java
常量15

CONSTANT_NameAndType_info
它的具体结构是:
CONSTANT_NameAndType_info{
u1 tag;
u2 name_index;//指向该方法或字段名称常量项的索引
u2 type_index;//指向其字段或方法描述符的常量索引
}
所以这个常量的所有字节码就是

通过结构可知:
tag = 0x0C = 12
name_index = 0x07 = 7 -> 指向常量7 -> <init>
type_index = 0x08 = 8 -> 指向常量8 -> ()V -> 基本数据类型void
()->方法的参数列表 V 返回值Void
此变量表示没有形参且返回值为void的构造函数 ->
没有定义构造函数的时候,Java会自动帮我们生成一个无参构造函数
常量16

通过结构可知 : tag = 0x0C = 12
name_index = 0x05 = 5 -> 指向常量5 -> age
type_index = 0x08 = 6 -> 指向常量6 -> I -> 基本数据类型int
此变量表示 int 类型的 age 参数
常量17

通过结构可知
tag = 0x01 = 1
length = 0x1D = 29 有29个utf8字节
byets = 63 6F 6D 2F 6E 65 6F 2F 61 73 6D 74 65 73 74 2F 42 79 74 65 63 6F 64 65 2F 44 65 6D 6F -> com/neo/asmtest/Bytecode/Demo
常量18

通过结构可知
tag = 0x01 = 1
length = 0x10 = 16 有16个utf8字节
byets = 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 -> java/lang/Object
常量池分析结束.~~~
访问标识

见下表:

00 21 -> 0x01 + 0x20 -> ACC_SUPER + ACC_PUBLIC
可通过取与操作,判断这个标识符是否被某标示符修饰。
例如:
0x21的二进制100001 -> ACC_PUBLIC的二进制1 -> 100001&1 = 1 ->是包含ACC_PUBLIC访问表示符
类索引

父类索引

接口计数器

接口索引集合
接口索引集合是一个集合,包含了所有实现的接口的索引,每个接口索引占用2个字节,指向常量中的接口。
由于Demo.java没有实现任何接口,所以不存在这部分的值。
字段个数

字段表

field_info{
u2 access_flags;//访问标志
u2 name_index;//字段名索引
u2 descriptor_index;//描述符索引
u2 attributes_count;//属性计数器
attribute_info attributes;//属性集合
}
00 02 00 05 00 06 00 00
access_flags = 0x0002 -> 2 -> 0010 -> ACC_PRIVATE -> private
name_index = 0x00 05 -> 5 -> 指向常量5 -> age
descriptor_index = 0x00 06 -> 6 -> 指向常量6 -> I ->表示int类型
attributes_count = 0x00 00 -> 0 -> attribute_info不存在
结果:
private int a
方法计数器

无参构造函数与getAge()
方法表
方法表结构如下:
method_info{
u2 access_flags; //方法访问标志
u2 name_index; //方法名称索引
u2 descriptor_index; //方法描述符索引
u2 attributes_count; //属性计数器
struct attribute_info{
u2 attribute_name_index; //属性名的索引
u4 attribute_length; //属性的长度
u2 max_stack;//操作数栈深度的最大值
u2 max_locals;//局部变量表所需的存续空间
u4 code_length;//字节码指令的长度
u1 code; //code_length个code,存储字节码指令
u2 exception_table_length;//异常表长度
exception_info exception_table;//exception_length个exception_info,组成异常表
u2 attributes_count;//属性集合计数器
attribute_info attributes;//attributes_count个attribute_info,组成属性表
}
}
方法1

access_flags = 0x00 01 -> 1 -> public
name_index = 0x00 07 -> 7 -> 指向常量7 -> <init>
descriptor_index = 0x00 08 -> 8 ->指向常量8 -> ()V
attributes_count = 0x00 01 -> 1 -> 记录着该方法有几个属性 -> 1个属性
00 09 00 00 00 1D 00 01 00 01 00 00 00 05
attribute_name_index = 0x00 09 -> 9 -> 指向常量9 -> Code
attribute_length = 0000001D -> 29 -> 29个字节
max_stack = 0x00 01 -> 1
max_locals = 0x00 01 -> 1
code_length = 00000005 -> 5个字节
2A B7 00 01 B1
code -> 0x2A B7 00 01 B1 ->
不能转换成十进制取看类,需要把十六进制转换成指令集
->结果如下:
2A -> aload_0 //从局部变量0中装载引用类型值入栈
B7 -> invokespecia //编译时方法绑定调用方法
00 -> nop //空操作
01 -> aconst_null //null值入栈
B1 -> return //void函数返回。
00 00 00 01
exception_table_length = 0x00 -> 0
这里存放的是处理异常的信息。 每个exception_table表项由start_pc,end_pc,handler_pc,catch_type组成。start_pc和end_pc表示在code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理;handler_pc表示处理异常的代码的开始处。catch_type表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有的异常,这个可以用来实现finally的功能
attributes_count = 0x00 01 -> 1 -> 表示有一个附加属性
attribute_info 结构如下:
attribute_info{
u2 attribute_name_index;
u4 attribute_length;
u2 line_tab_table_length
}
line_tab_table_length{
u2 start_pc;
u2 line_number;
}

00 0A 00 00 00 06 00 01 00 00 00 03
attribute_name_index = 0x00 0A -> 10 -> 指向常量10 -> LineNumberTable
attribute_length = 0x00 00 00 06-> 6个字节 -> 后面有6个字节的属性
line_tab_table_length = 0x00 01 -> 1代表LineNumberTable有一项值
start_pc = 0x00 00 -> 0,代表字节码行号
line_number = 0x 00 03 -> 3 -> 代表java源码的行号为第3行
方法2

access_flags = 0x00 01 -> 1 -> public
name_index = 0x00 0B -> 11 -> 指向常量11 -> getAge
descriptor_index = 0x00 0C -> 12 ->指向常量12 -> ()I
attributes_count = 0x00 01 -> 1 -> 记录着该方法有几个属性 -> 1个属性
00 09 00 00 00 1D 00 01 00 01 00 00 00 05
attribute_name_index = 0x00 09 -> 9 -> 指向常量9 -> Code
attribute_length = 0x0000001D -> 29 -> 29个字节
max_stack = 0x00 01 -> 1
max_locals = 0x00 01 -> 1
code_length = 0x00000005 -> 5个字节
2A B4 00 02 AC
code -> 0x2A B4 00 02 AC ->
不能转换成十进制取看类,需要把十六进制转换成指令集 ->结果如下:
2A -> aload_0 //从局部变量0中装载引用类型值入栈
B4 -> getfield // 获取对象字段的值
00 -> nop // 空操作
02 -> iconst_m1 //-1(int)值入栈
AC -> ireturn //返回int类型值
00 00 00 01
exception_table_length = 0x00 -> 0
attributes_count = 0x00 01 -> 1 -> 表示有一个附加属性

00 0A 00 00 00 06 00 01 00 00 00 08
attribute_name_index = 0x00 0A -> 10 -> 指向常量10 -> LineNumberTable
attribute_length = 0x00 00 00 06-> 6个字节 -> 后面有6个字节的属性
line_tab_table_length = 0x00 01 -> 1代表LineNumberTable有一项值
start_pc = 0x00 00 -> 0,代表字节码行号
line_number = 0x 00 08 -> 8 -> 代表java源码的行号为第8行
附加属性-属性个数

附加属性-属性结构

SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
attribute_name_index = 0x00 0D = 13 -> 指向常量13 -> SourceFile
attribute_length = 0x00 00 00 02 = 2 -> 后面有2字节内容
sourcefile_index = 0x00 0E = 14 -> 指向常量14 -> Demo.java
代表着个class字节码文件的源码名为Demo.java