上一篇我们分析了字节码文件加载到虚拟机的过程,在继续深入class文件类解析前先了解下class文件格式。Java虚拟机中每一个class文件都对应着唯一一个类或接口的定义信息。每个class文件由字节流组成。在描述class文件格式时使用类c伪结构体。结构体中的类数组表是由任意数量的可变长度的项组成的复合结构,表中的每项长度不固定。
一,Java字节码文件
1. ClassFile 结构
ClassFile {
u4 magic; //魔数 0xCAFEBABE
u2 minor_version; //副版本号
u2 major_version; //主版本号
u2 constant_pool_count; //常量池计数器
//常量池,表结构,包含class文件结构及子结构引用的所有字符串常量·类·接口名·字段名和其他常量
//也就是其他结构引用到的符号都到常量池来找
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.常量池
常量池表constant_pool中存储的是不定结构项cp_info
cp_info {
u1 tag; //代表存储类型
u1 info[]; //内容有tag类型决定
}
常量池表中可存储的类型,每种类型都对应一个结构体,如CONSTANT_Class对应CONSTANT_Class_info
Constant Kind Tag 描述
CONSTANT_Class 7 类
CONSTANT_Fieldref 9 字段
CONSTANT_Methodref 10 方法
CONSTANT_InterfaceMethodref 11 接口方法
CONSTANT_String 8 字符串
CONSTANT_Integer 3 整型
CONSTANT_Float 4 浮点型
CONSTANT_Long 5 长整型
CONSTANT_Double 6 双精度浮点型
CONSTANT_NameAndType 12 二进制名称
CONSTANT_Utf8 1 utf8编码
CONSTANT_MethodHandle 15 方法句柄
CONSTANT_MethodType 16 方法类型
CONSTANT_Dynamic 17 动态计算常数(ldc)
CONSTANT_InvokeDynamic 18 动态计算调用(invokedynamic)
CONSTANT_Module 19 模块
CONSTANT_Package 20 包
3.字段
每一个字段都对应一个field_info结构
field_info {
u2 access_flags; //字段权限(由掩码构成标志位)
u2 name_index; //字段类型(指向常量池中的CONSTANT_Utf8_info项)
u2 descriptor_index; //字段描述(指向常量池中的CONSTANT_Utf8_info项)
u2 attributes_count; //字段属性计数器
attribute_info attributes[attributes_count]; //属性表(字段注解之类的属性)
}
4. 方法
所有方法,包括类接口初始化方法在内,每个方法对应一个method_info结构
method_info {
u2 access_flags; //方法权限(由掩码构成标志位)
u2 name_index; //方法类型(指向常量池中的CONSTANT_Utf8_info项)
u2 descriptor_index; //方法类型(指向常量池中的CONSTANT_Utf8_info项)
u2 attributes_count; //方法属性计数器
attribute_info attributes[attributes_count];//属性表(方法注解之类的属性)
}
}
5. 属性
属性结构attribute_info被用于ClassFile,field_info,method_info,Code_attribute结构中,attribute_info结构与常量池类似
attribute_info {
u2 attribute_name_index; //属性类型
u4 attribute_length; //属性长度
u1 info[attribute_length]; //内容由attribute_name_index类型决定
}
除自定义属性外,属性分为三类:
a.虚拟机能够正确读取class文件的关键属性
1.ConstantValue //常量表达式,定长属性,位于field_info,静态字段会赋值为ConstantValue表示的值
2.Code //码点,边长属性,位于method_info,包含虚拟机指令
3.StackMapTable //类型验证,边长属性,位于Code属性表
4.BootstrapMethods //invokedynamic引导方法限定符,位于ClassFile结构
5.NestHost //类或接口嵌套,定长属性,位于ClassFile结构
6.NestMembers //类或接口的嵌套成员权限,边长属性,位于ClassFile结构
b.Java平台类库正确解读class文件关键属性
1.Exceptions //受检异常,变长属性,位于method_info
2.InnerClass //内部类,变长属性,位于ClassFile结构
3.EnclosingMethod//局部类/匿名类,可选定长属性,位于ClassFile结构
4.Synthetic //编译器合成,定长属性,位于ClassFile,field_info,method_info结构
5.Signature //泛型签名信息,定长属性,位于ClassFile,field_info,method_info结构
6.SourceFile //定长属性,位于ClassFile结构
7.LineNumberTable //调试,可选边长属性,位于Code结构
8.LocalVariableTable //局部变量描述信息,可选变长属性,位于Code结构
9.LocalVariableTypeTable //参数化类型变量,可选变长属性,位于Code结构
c.Java实用工具属性
1.SourceDebugExtension //代码调试,可选属性,位于ClassFile结构
2.Deprecated //过期声明,可选定长属性,位于ClassFile,field_info,method_info结构
3.RuntimeVisibleAnnotations //运行时可见注解,变长属性,位于ClassFile,field_info,method_info结构
4.RuntimeInvisibleAnnotations //运行时不可见注解,变长属性,位于ClassFile,field_info,method_info结构
5.RuntimeVisibleParameterAnnotations //运行时可见形式参数注解,变长属性,位于method_info结构
6.RuntimeInvisibleParameterAnnotations //运行时不可见形式参数注解,变长属性,位于method_info结构
7.RuntimeVisibleTypeAnnotations //运行时不可见类型注解,变长属性,位于ClassFile,field_info,method_info,Code结构
8.RuntimeInvisibleTypeAnnotations //运行时不可见类型注解,变长属性,位于ClassFile,field_info,method_info,Code结构
9.AnnotationDefault //注解类型中的元素,变长属性,位于method_info结构
10.MethodParameters //形式参数信息,变长属性,位于method_info结构
11.Module //模块信息,变长属性,位于ClassFile结构
12.ModulePackages //模块导出的包,变长属性,位于ClassFile结构
13.ModuleMainClass //模块主类,定长属性,位于ClassFile结构
Java编译器将Java类文件编译成.class后缀的固定格式的二进制字节码文件。Java虚拟机从二进制流中加载字节码文件时会对文件格式进行检查。在解析Java类之前会先进行类的验证。
二,Java字节码文件分析
为了分析字节码文件,首先将原来调试HotSpot的Hello类添加一个add方法,然后使用编译成class文件。
public class Hello{
public void main(String[] args){
System.out.println("hello world !" + add(1,2));
}
private static int add(int a, int b){
int sum = 0;
sum = a + b;
return sum;
}
}
1. Classpy分析字节码
然后我们使用开源工具calsspy(https://github.com/zxh0/classpy) 来查看字节码文件。将classpy拖到本地后用IDEA打开,参考README在终端启动它。如下图可以方便查看字节码文件。
如上图标明的5部分分别是魔数,主次版本号,常量池计数器,常量池表。
如上图标注分别是类访问权限,当前类,父类,接口,字段,方法,属性
如上图标注的是add()方法结构信息,name_index 值为14,常量池索引为#14对应为add,码点属性中max_locals值为3,即存在a,b,sum三个局部变量,max_stacks为栈调用深度,code表中存了8条指令,即add方法的加法算数逻辑。无异常,存在行号表属性。
上图标注的是1. ClassFile结构中的this_class#6,对应常量池中表中的第6项数据07001C, tag:07表示这是一个类信息CONSTANT_Class_info,name_index:001C值28 对应常量池表第28项,即010023开头,后面跟UTF8码点表示Hello类全限定名。2.add方法名和返回类型分别对应常量池表第14和15。
2. jclasslib插件
其他字节码信息分析过程大致如此,除此之外还可以在IDEA中使用jclasslib插件查看如下图。
Java字节码文件的组织遵守静态约束和结构化约束,不符合规范的字节码文件是无法在虚拟机中运行的,这点实在虚拟机链接类阶段做出验证保证的。了解字节码文件格式为接下来的字节码文件解析做好准备,在此字节码文件解析之前,首先要了解一下HotSpot的内存结构模型。