JDK版本:8
原打算一步一步手动解析.java文件,生成.class文件的,但是笔者对这块实在不熟,遇到的阻碍很多,尤其是.class文件的常量池这块,常量的生成规则和生成顺序等问题。说白了就是还没摸清套路,所以被迫放低要求,先学会阅读编译器编译生成的.class文件,为日后手动解析.java文件打下基础。
基础知识
java应用程序运行原理
- .java文件通过编译器编译,生成.class文件(字节码文件)
- .class文件通过类加载器子系统加载,在元空间生成类的元信息(InstanceKlass)
- 类的元信息通过类加载器子系统验证、准备、解析、初始化,在JVM堆生成Class对象
- Class对象通过执行引擎执行
.class文件涉及的数据类型
基础类型
u1 // 1个字节的数据类型
u2 // 2个字节的数据类型
u4 // 4个字节的数据类型
u8 // 8个字节的数据类型
常量池的数据类型结构
attribute的封装类
类的成员有属性、方法等,这里讨论的属性不是类的属性成员,而是类的属性成员和类的方法成员的属性。为作区分,本文将类的属性成员称作field,类的方法成员称作method,field和method的属性称作attribute。
在.class文件维度,attribute的封装类有三个,分别是attribute_info、Code_attribute和SourceFile_attribute
- attribute_info用来描述普通的attribute
- Code_attribute用来描述method的第一个attribute,比如一个method有两个attribute,那第一个是Code_attribute结构,第二个是attribute_info结构
- SourceFile_attribute用来描述类的附加信息
attribute_info、Code_attribute和SourceFile_attribute的具体内部结构
attribute_info { // 用来描述普通的attribute
u2 attribute_name_index; // 属性名的间接引用(间接引用就是指向.class文件的常量池的指针)
u4 attribute_length; // 表示后面还有多少长度的字节属于当前attribute_info
u1 info[attribute_length]; // 具体attribute信息数组
}
Code_attribute { // 用来描述method的第一个attribute
u2 attribute_name_index; // 属性名的间接引用
u4 attribute_length; // 表示后面还有多少长度的字节属于当前Code_attribute
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)
attribute_info attributes[attributes_count];
}
SourceFile_attribute { // 用来描述类的附加信息
u2 attribute_name_index; // 属性名的间接引用
u4 attribute_length; // 表示后面还有多少长度的字节属于当前SourceFile_attribute
u2 sourcefile_index; // 属性值的间接引用
}
field的封装类
field_info {
u2 access_flags; // 访问修饰符,详见附件一
u2 name_index; // field名的间接引用
u2 descriptor_index; // 属性类型的间接引用,详见附件二
u2 attributes_count; // 属性数量(后面跟了几个attribute_info)
attribute_info attributes[attributes_count];
}
method的封装类
method_info {
u2 access_flags; // 访问修饰符,详见附件一
u2 name_index; // method名的间接引用
u2 descriptor_index; // 方法签名的间接引用
u2 attributes_count; // 属性数量
attribute_info attributes[attributes_count]; // 第一个是Code_attribute 后面是attribute_info
}
.class文件的结构
"!"表示长度不确定,如果没有则跳过
u4 魔数
u2 次版本号
u2 主版本号
u2 常量池大小
! 常量池
u2 类的访问控制权限
u2 类名
u2 父类名
u2 实现的接口数量
! 实现的接口信息数组
u2 field数量
! field信息数组
u2 method数量
! method信息数组
u2 类的附加信息数量
! 类的附加信息数组
分析.class文件
一个简单的java类代码
package com.front.worksheet;
public class ApplicationTest {
static int num = 10;
public static void main(String[] args) {
}
}
编译后的.class文件
CA FE BA BE 00 00 00 34 00 1A 0A 00 04 00 16 09
00 03 00 17 07 00 18 07 00 19 01 00 03 6E 75 6D
01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03
28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E
65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C
6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C
65 01 00 04 74 68 69 73 01 00 25 4C 63 6F 6D 2F
66 72 6F 6E 74 2F 77 6F 72 6B 73 68 65 65 74 2F
41 70 70 6C 69 63 61 74 69 6F 6E 54 65 73 74 3B
01 00 04 6D 61 69 6E 01 00 16 28 5B 4C 6A 61 76
61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
01 00 04 61 72 67 73 01 00 13 5B 4C 6A 61 76 61
2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 01 00 10
4D 65 74 68 6F 64 50 61 72 61 6D 65 74 65 72 73
01 00 08 3C 63 6C 69 6E 69 74 3E 01 00 0A 53 6F
75 72 63 65 46 69 6C 65 01 00 14 41 70 70 6C 69
63 61 74 69 6F 6E 54 65 73 74 2E 6A 61 76 61 0C
00 07 00 08 0C 00 05 00 06 01 00 23 63 6F 6D 2F
66 72 6F 6E 74 2F 77 6F 72 6B 73 68 65 65 74 2F
41 70 70 6C 69 63 61 74 69 6F 6E 54 65 73 74 01
00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65
63 74 00 21 00 03 00 04 00 00 00 01 00 08 00 05
00 06 00 00 00 03 00 01 00 07 00 08 00 01 00 09
00 00 00 2F 00 01 00 01 00 00 00 05 2A B7 00 01
B1 00 00 00 02 00 0A 00 00 00 06.00 01 00 00 00
03 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00
0D 00 00 00 09 00 0E 00 0F 00 02 00 09 00 00 00
2B 00 00 00 01 00 00 00 01 B1 00 00 00 02 00 0A
00 00 00 06 00 01 00 00 00 0E 00 0B 00 00 00 0C
00 01 00 00 00 01 00 10 00 11 00 00 00 12 00 00
00 05 01 00 10 00 00 00 08 00 13 00 08 00 01 00
09 00 00 00 1E 00 01 00 00 00 00 00 06 10 0A B3
00 02 B1 00 00 00 01 00 0A 00 00 00 06 00 01 00
00 00 04 00 01 00 14 00 00 00 02 00 15
u4 魔数
CA FE BA BE
咖啡baby(与java的logo呼应),是java类的身份标识,这个值是写死的
u2 次版本号
00 00
u2 主版本号
00 34
十六进制34对应十进制52,配合次版本号00,表示JDK版本是1.8,详见下图
u2 常量池大小
00 1A
十六进制1A对应十进制26,表示常量池有25个常量(这个值总是比实际常量池数量多1,有人说第0位是null)
! 常量池
第1个常量 CONSTANT_Methodref_info u1+u2+u2结构
0A 00 04 00 16
CONSTANT_Methodref_info结构如下
CONSTANT_Methodref_info {
u1 tag; // 每个常量池的数据结构第一个值都是tag,因为一共只有11种,所以一个十六进制字节就够了
u2 index; // 指向一个CONSTANT_Class_info结构常量
u2 index; // 指向一个CONSTANT_NameAndType_info结构常量
}
十六进制0A对应十进制10:10代表CONSTANT_Methodref_info结构
十六进制00 04对应十进制4:指向常量池第4个常量
十六进制00 16对应十进制22:指向常量池第22个常量
第2个常量 CONSTANT_Fieldref_info u1+u2+u2结构
09 00 03 00 17
CONSTANT_Fieldref_info结构如下
CONSTANT_Fieldref_info {
u1 tag;
u2 index; // 指向一个CONSTANT_Class_info结构常量
u2 index; // 指向一个CONSTANT_NameAndType_info结构常量
}
十六进制09对应十进制9:9代表CONSTANT_Fieldref_info结构
十六进制00 03对应十进制3:指向常量池第3个常量
十六进制00 17对应十进制23:指向常量池第23个常量
第3个常量 CONSTANT_Class_info u1+u2结构
07 00 18
CONSTANT_Class_info结构如下
CONSTANT_Class_info {
u1 tag;
u2 index; // 指向一个CONSTANT_Utf8_info结构常量
}
十六进制07对应十进制7:7代表CONSTANT_Class_info结构
十六进制00 18对应十进制24:指向常量池第24个常量
第4个常量 CONSTANT_Class_info u1+u2结构
07 00 19
同上CONSTANT_Class_info结构
十六进制07对应十进制7:7代表CONSTANT_Class_info结构
十六进制00 19对应十进制25:指向常量池第25个常量
第5个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 03 6E 75 6D
CONSTANT_Utf8_info结构如下
CONSTANT_Class_info {
u1 tag;
u2 length; // 表示后面还有多少长度的字节属于当前CONSTANT_Class_info
u1 bytes[length]; // 具体内容(对应ascii码表,详见附件三)
}
十六进制01对应十进制1:1代表CONSTANT_Class_info结构
十六进制00 03对应十进制3:代表字符串长度为3
6E 75 6D可以直接从ascii码表中找到,内容是num:
第6个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 01 49
分析过程同上,内容是I
第7个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 06 3C 69 6E 69 74 3E
分析过程同上,内容是<init>
第8个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 03 28 29 56
分析过程同上,内容是()V
第9个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 04 43 6F 64 65
分析过程同上,内容是Code
第10个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
分析过程同上,内容是LineNumberTable
第11个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65
分析过程同上,内容是LocalVariableTable
第12个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 04 74 68 69 73
分析过程同上,内容是this
第13个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 25 4C 63 6F 6D 2F 66 72 6F 6E 74 2F 77 6F 72 6B 73 68 65 65 74 2F 41 70 70 6C 69 63 61 74 69 6F 6E 54 65 73 74 3B
分析过程同上,内容是Lcom/front/worksheet/ApplicationTest;
第14个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 04 6D 61 69 6E
分析过程同上,内容是main
第15个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
分析过程同上,内容是([Ljava/lang/String;)V
第16个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 04 61 72 67 73
分析过程同上,内容是args
第17个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 13 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B
分析过程同上,内容是[Ljava/lang/String;
第18个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 10 4D 65 74 68 6F 64 50 61 72 61 6D 65 74 65 72 73
分析过程同上,内容是MethodParameters
第19个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 08 3C 63 6C 69 6E 69 74 3E
分析过程同上,内容是<clinit>
第20个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 0A 53 6F 75 72 63 65 46 69 6C 65
分析过程同上,内容是SourceFile
第21个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 14 41 70 70 6C 69 63 61 74 69 6F 6E 54 65 73 74 2E 6A 61 76 61
分析过程同上,内容是ApplicationTest.java
第22个常量 CONSTANT_NameAndType_info u1+u2+u2结构
0C 00 07 00 08
CONSTANT_NameAndType_info结构如下
CONSTANT_NameAndType_info {
u1 tag;
u2 index; // Name
u2 index; // Descriptor
}
十六进制0C对应十进制12:12代表CONSTANT_NameAndType_info结构
十六进制00 07对应十进制7:指向常量池第7个常量
十六进制00 08对应十进制8:指向常量池第8个常量
第23个常量 CONSTANT_NameAndType_info u1+u2+u2结构
0C 00 05 00 06
同上CONSTANT_NameAndType_info结构
十六进制00 05对应十进制5:指向常量池第5个常量
十六进制00 06对应十进制6:指向常量池第6个常量
第24个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 23 63 6F 6D 2F 66 72 6F 6E 74 2F 77 6F 72 6B 73 68 65 65 74 2F 41 70 70 6C 69 63 61 74 69 6F 6E 54 65 73 74
分析过程同上,内容是com/front/worksheet/ApplicationTest
第25个常量 CONSTANT_Utf8_info u1+u2+u1结构
01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
分析过程同上,内容是java/lang/Object
u2 类的访问控制权限
00 21(详见附件一)
u2 类名
00 03
类全限定名的间接引用
u2 父类名
00 04
类的父类全限定名的间接引用
u2 实现的接口数量
00 00
! 实现的接口信息数组
因为该类没有实现任何接口,所以这个区域没有数据,直接跳过
u2 field数量
00 01
类成员中有一个num属性,所有会有一个field_info结构
! field信息数组
field_info {
u2 access_flags; 00 08(详见附件一)
u2 name_index; 00 05(指向常量池第5个常量,内容是num)
u2 descriptor_index; 00 06(指向常量池第6个常量,内容是I,I代表int类型,详见附件二)
u2 attributes_count; 00 00
attribute_info attributes[attributes_count];
}
u2 method数量
00 03
一共有三个方法:默认构造函数(init方法)、main函数、有静态成员的类会自动生成静态构造函数(clinit方法)
! method信息数组
第1个方法
method_info {
u2 access_flags; 00 01(详见附件一)
u2 name_index; 00 07(指向常量池第7个常量,内容是<init>)
u2 descriptor_index; 00 08(指向常量池第8个常量,内容是()V)
u2 attributes_count; 00 01(后面有一个attribute_info结构)
attribute_info attributes[attributes_count];
}
Code_attribute {
u2 attribute_name_index; 00 09(指向常量池第9个常量,内容是Code)
u4 attribute_length; 00 00 00 2F(十六进制2F对应十进制47)
u2 max_stack; 00 01
u2 max_locals; 00 01
u4 code_length; 00 00 00 05(方法体长度)
u1 code[code_length]; 2A B7 00 01 B1(2A:aload_0 详见下图一;
B7:invokespecial 详见下图二;00 01指向常量池第1个常量;
B1:return 详见下图三)
u2 exception_table_length; 00 00
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count; 00 02
attribute_info attributes[attributes_count];
}
LineNumberTable_attribute {
u2 attribute_name_index; 00 0A(指向常量池第10个常量,内容是LineNumberTable,说明这个attribute是LineNumberTable结构)
u4 attribute_length; 00 00 00 06(后面还有多少长度的字节属于当前LineNumberTable_attribute)
u2 line_number_table_length; 00 01(有一行属性)
{
u2 start_pc; 00 00(字节码指令索引值,也就是程序计数器计的值)
u2 line_number; 00 03(java代码所在行数)
} line_number_table[line_number_table_length];
}
LocalVariableTable_attribute {
u2 attribute_name_index; 00 0B(指向常量池第11个常量,内容是LocalVariableTable,说明这个attribute是LocalVariableTable结构)
u4 attribute_length; 00 00 00 0C(后面还有多少长度的字节属于当前LocalVariableTable_attribute)
u2 local_variable_table_length; 00 01(有一行属性)
{
u2 start_pc; 00 00(字节码指令索引值)
u2 length; 00 05(该变量存活的长度,活到多少个字节运行完;这个方法方法体是2A B7 00 01 B1,5个字节,因为this是方法入参,默认构造方法第一个参数就是this,所以存活到该方法执行结束)
u2 name_index; 00 0C(指向常量池第12个常量,内容是this)
u2 descriptor_index; 00 0D(指向常量池第13个常量,内容是Lcom/front/worksheet/ApplicationTest;)
u2 index; 00 00(变量的索引值)
} local_variable_table[local_variable_table_length];
}
第2个方法 分析过程同上
method_info {
u2 access_flags; 00 09
u2 name_index; 00 0E
u2 descriptor_index; 00 0F
u2 attributes_count; 00 02
attribute_info attributes[attributes_count];
}
Code_attribute {
u2 attribute_name_index; 00 09
u4 attribute_length; 00 00 00 2B
u2 max_stack; 00 00
u2 max_locals; 00 01
u4 code_length; 00 00 00 01
u1 code[code_length]; B1
u2 exception_table_length; 00 00
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count; 00 02
attribute_info attributes[attributes_count];
}
attribute_info {
u2 attribute_name_index; 00 0A
u4 attribute_length; 00 00 00 06
u1 info[attribute_length]; 00 01 00 00 00 0E
}
attribute_info {
u2 attribute_name_index; 00 0B
u4 attribute_length; 00 00 00 0C
u1 info[attribute_length]; 00 01 00 00 00 01 00 10 00 11 00 00
}
attribute_info {
u2 attribute_name_index; 00 12
u4 attribute_length; 00 00 00 05
u1 info[attribute_length]; 01 00 10 00 00
}
第3个方法 分析过程同上
method_info {
u2 access_flags; 00 08
u2 name_index; 00 13
u2 descriptor_index; 00 08
u2 attributes_count; 00 01
attribute_info attributes[attributes_count];
}
Code_attribute {
u2 attribute_name_index; 00 09
u4 attribute_length; 00 00 00 1E
u2 max_stack; 00 01
u2 max_locals; 00 00
u4 code_length; 00 00 00 06
u1 code[code_length]; 10 0A B3 00 02 B1
u2 exception_table_length; 00 00
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count; 00 01
attribute_info attributes[attributes_count];
}
attribute_info {
u2 attribute_name_index; 00 0A
u4 attribute_length; 00 00 00 06
u1 info[attribute_length]; 00 01 00 00 00 04
}
u2 类的附加信息数量
00 01
! 类的附加信息数组
SourceFile_attribute {
u2 attribute_name_index; 00 14(指向常量池第20个常量,内容是SourceFile)
u4 attribute_length; 00 00 00 02
u2 sourcefile_index; 00 15(指向常量池第21个常量,内容是ApplicationTest.java)
}
十六进制14对应十进制20
十六进制15对应十进制21