上一篇我们分析了字节码文件加载到虚拟机的过程,在继续深入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在终端启动它。如下图可以方便查看字节码文件。

java的字节码文件以什么后缀结尾 java字节码文件名的后缀_java的字节码文件以什么后缀结尾


如上图标明的5部分分别是魔数,主次版本号,常量池计数器,常量池表。

java的字节码文件以什么后缀结尾 java字节码文件名的后缀_java_02


如上图标注分别是类访问权限,当前类,父类,接口,字段,方法,属性

java的字节码文件以什么后缀结尾 java字节码文件名的后缀_常量池_03


如上图标注的是add()方法结构信息,name_index 值为14,常量池索引为#14对应为add,码点属性中max_locals值为3,即存在a,b,sum三个局部变量,max_stacks为栈调用深度,code表中存了8条指令,即add方法的加法算数逻辑。无异常,存在行号表属性。

java的字节码文件以什么后缀结尾 java字节码文件名的后缀_java的字节码文件以什么后缀结尾_04

上图标注的是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的字节码文件以什么后缀结尾 java字节码文件名的后缀_hotspot_05


Java字节码文件的组织遵守静态约束和结构化约束,不符合规范的字节码文件是无法在虚拟机中运行的,这点实在虚拟机链接类阶段做出验证保证的。了解字节码文件格式为接下来的字节码文件解析做好准备,在此字节码文件解析之前,首先要了解一下HotSpot的内存结构模型。