前言
我们常说Java是跨平台的语言,主要原因是有JVM。可以说,只要有能运行JVM的平台,Java代码基本可以无缝移植,而跨平台的基石便是Class文件。
本文旨在解析Class文件数据结构,加深对jvm的理解。
环境:
操作系统:win10
JDK版本:1.8.0_291
本文所有内容都是基于以上环境的,如果有异议的地方欢迎邮件交流。
Class类文件结构
Class文件是一组以8bit为基础的字节流,多数据项之间严格按照数据紧凑的排列在Class文件中,没有分割符。如果数据项占用超过8bit,那么以Big-Endian
方式分割成若干个8bit存储。
根据JVM规定,Class文件采用一种类型C结构体的伪结构体来存储数据,这种结构体中只有两种数据类型:无符号数
和表
- 无符号数:以u1、u2、u4、u8代表1Byte、2Byte、4Byte、8Byte的无符号数,用于描述数字、索引、数量或者按照UTF-8编码构成的字符串
- 表:_info结尾,由多个无符号数和其他表构成的复合数据结构,用于描述有层次关系的符合数据结构
1Byte = 8bit
下面Class文件结构伪代码:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
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];
}
属性解释:
magic
魔数,标识文件类型,值是0xCAFEBABE
minor_version,major_version
major_version:主版本号;minor_version:次版本号。高版本JDK能向下兼容以前的版本的Class文件,不能兼容之后的,例如:JDK1.0.2能支持的版本号为45.0-45.3,不能支持45.65535版本Class文件;JDK1.1.*能支持45.0-45.65535
constant_pool_count
代表常量池容量计数值,这个技术值从1开始,而不是0。假设这个值是10,那么代表常量池有9个常量,索引范围是1-9。而0则是为了满足某些指向常量池索引表达为“不引用任何常量池项”的特定情况
constant_pool[]
代表实际存放常量池的数组,大小是constant_pool_count,索引范围(1-constant_pool_count-1),具体如下图:
cp_info的结构
常量池通用结构:
cp_info{ u1 tag; u1 info[]; }
JVM虚拟机规定了不同的tag值和不同类型的字面量对应关系如下:
Constant Type Value 涵义 CONSTANT_Class 7 类或者接口的全限定名 CONSTANT_Fieldref 9 表示类中的字段 CONSTANT_Methodref 10 表示类中方法 CONSTANT_InterfaceMethodref 11 表示类中实现的接口的方法 CONSTANT_String 8 表示java.lang.String类型的常量对象 CONSTANT_Integer 3 表示4字节(int)的数值常量 CONSTANT_Float 4 表示4字节(float)的数值常量 CONSTANT_Long 5 表示8字节(long)的数值常量 CONSTANT_Double 6 表示8字节(double)的数值常量 CONSTANT_NameAndType 12 表示字段或者方法的名称或者类型 CONSTANT_Utf8 1 表示字符串常量的值 CONSTANT_MethodHandle 15 表示方法句柄 CONSTANT_MethodType 16 表示方法类型 CONSTANT_InvokeDynamic 18 表示invokedynamic指令所用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及可选择性附加被称为静态参数(Static Arguments)的常量序列 - CONSTANT_Class_info
CONSTANT_Class_info{ //值为7 u1 tag; //指向一个CONSTANT_Utf8_info结构体的索引,这个结构体存储具体的值 u2 name_index; }
- CONSTANT_Fieldref_info,CONSTANT_Methodref_info,CONSTANT_InterfaceMethodref_info
CONSTANT_Fieldref_info { //值为9 u1 tag; //指向CONSTANT_Class_info结构体的索引,类型可以是类也可以是接口 u2 class_index; //指向一个CONSTANT_NameAndType_info结构体的索引,并且这个结构体中描述符必须是字段描述符 u2 name_and_type_index; } CONSTANT_Methodref_info { //值为10 u1 tag; //指向CONSTANT_Class_info结构体的索引,类型必须是类,不可以是接口 u2 class_index; //指向一个CONSTANT_NameAndType_info结构体的索引,并且这个结构体中描述符必须是方法描述符;如果以'<'('\u003c')开头,一定是<init>这个特殊方法,返回值是void。 u2 name_and_type_index; } CONSTANT_InterfaceMethodref_info { //值为11 u1 tag; //指向CONSTANT_Class_info结构体的索引,类型必须是接口,不可以是类 u2 class_index; //指向一个CONSTANT_NameAndType_info结构体的索引,并且这个结构体中描述符必须是方法描述符 u2 name_and_type_index; }
- CONSTANT_String_info
CONSTANT_String_info { //值为8 u1 tag; //指向一个CONSTANT_Utf8_info结构体的索引,这个结构体存储具体的值 u2 string_index; }
- CONSTANT_Integer_info,CONSTANT_Float_info
CONSTANT_Integer_info { //值为3 u1 tag; //存储整数值 u4 bytes; } CONSTANT_Float_info { //值为4 u1 tag; //存储浮点值 u4 bytes; }
- CONSTANT_Long_info,CONSTANT_Double_info
具体可见LONG如何存储
CONSTANT_Long_info { //值为5 u1 tag; //高4位 u4 high_bytes; //低4位 u4 low_bytes; } CONSTANT_Double_info { //值为6 u1 tag; u4 high_bytes; u4 low_bytes; }
- CONSTANT_NameAndType_info
CONSTANT_NameAndType_info { //值为12 u1 tag; //指向一个CONSTANT_Utf8_info结构体的索引,包含特殊方法<init>,或者方法和字段的非全限定名,即短名。 u2 name_index; //指向一个CONSTANT_Utf8_info结构体的索引,代表字段或者方法 u2 descriptor_index; }
- CONSTANT_Utf8_info
CONSTANT_Utf8_info { //值为12 u1 tag; //长度 u2 length; //字节数组 u1 bytes[length]; }
- CONSTANT_MethodHandle_info
CONSTANT_MethodHandle_info { //值为15 u1 tag; //值在1-9之间,值表示方法句柄的类型及其字节码行为 u1 reference_kind; //值是常量池的有效索引 //如果reference_kind项的值为1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic),那么常量池在reference_index索引处的项必须是CONSTANT_Fieldref_info结构,表示由一个字段创建的方法句柄 //如果reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或8(REF_newInvokeSpecial),那么常量池在reference_index索引处的项必须是CONSTANT_Methodref_info结构,表示由类的方法或构造函数创建的方法句柄 //如果reference_kind项的值是9(REF_invokeInterface),那么常量池在reference_index索引处的项必须是CONSTANT_InterfaceMethodref_info结构,表示由接口方法创建的方法句柄 //如果reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface),那么方法句柄对应的方法不能为实例初始化()方法或类初始化方法() //如果reference_kind项的值是8(REF_newInvokeSpecial),那么方法句柄对应的方法必须为实例初始化<init> u2 reference_index; }
- CONSTANT_MethodType_info
CONSTANT_MethodType_info { //值为16 u1 tag; //必须是CONSTANT_Utf8_info结构,代表方法的描述符 u2 descriptor_index; }
- CONSTANT_InvokeDynamic_info
// 表示invokedynamic指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及可以选择性的附加被称为静态参数(Static Arguments)的常量序列。 CONSTANT_InvokeDynamic_info { //值为18 u1 tag; //对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引 u2 bootstrap_method_attr_index; 对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info 结构,表示方法名和方法描述符 u2 name_and_type_index; }
综上,就是1.8版本jvm字节码部分常量池所有类型,像最后三个有点抽象,暂时先放在一边,等后续我们在详谈
access_flags
标志名称 标志值 含义 适用范围 ACC_PUBLIC 0x0001 声明为public,可以从包外访问 所有类型 ACC_FINAL 0x0010 声明为final,不允许有子类 类 ACC_SUPER 0x0020 调用Invokescial指令对超类方法进行处理 类和接口 ACC_INTERFACE 0x0200 声明是接口类型而不是类 接口 ACC_ABSTRACT 0x0400 声明是抽象类型,不能被实例化 接口或抽象类 ACC_SYNTHETIC 0x1000 声明是由编译器生成的,不出现在源码中 所有类型 ACC_ANNOTATION 0x2000 声明为注解类型 注解 ACC_ENUM 0x4000 声明为枚举类型 枚举 this_class
指向一个CONSTANT_Class_info的常量池索引,代表该类或者接口信息
super_class
如果值是0,那么代表Object类;如果不是0,值为一个指向CONSTANT_Class_info的索引,代表该类的父类
interfaces_count
接口计数器,代表实现接口数量,如果没事实现接口计数器值是0
interfaces[]
接口索引数组,每个索引指向一个CONSTANT_Class_info结构,代表接口信息,按照源码中从左到右排序
fields_count
字段计数器,代表field_info结构的数量
fields[]
field_info的集合,代表类或者接口的完整描述。注意,只包含本身类中的,不包含继承的字段。
field_info结构
每个字段都是由field_info结构来描述,并且一个类文件中不能有两个字段具有相同的名称和描述符
字段描述符
字段描述符表示类、实例或局部变量的类型
FieldDescriptor: FieldType FieldType: BaseType ObjectType ArrayType BaseType: (one of) B C D F I J S Z ObjectType: L ClassName ; ArrayType: [ ComponentType ComponentType: FieldType
下面的表格解释上面的作用
FieldType术语 类型 解释 B byte 有符号的byte类型,-127-128 C char 基本多语言平面中的Unicode字符编码点,使用UTF-16编码 D double 双精度浮点 F float 单精度浮点 I int 整数 J long 长整数 L ClassName ; reference 类ClassName的实例 S short 带符号的short类型 Z boolean true or false [ reference 一维数组,二维数组用[[表示 field_info结构:
field_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
access_flags:
表示字段的访问权限和属性,具体如下表:
名称 值 解释 ACC_PUBLIC 0x0001 声明public,可以从包外访问 ACC_PRIVATE 0x0002 声明privcate,仅能在定义类中使用 ACC_PROTECTED 0x0004 声明protected,可以在子类中访问 ACC_STATIC 0x0008 声明static ACC_FINAL 0x0010 声明final,在对象构造之后,不能直接赋值 ACC_VOLATILE 0x0040 声明volatile,不能被缓存 ACC_TRANSIENT 0x0080 声明transient,不能由持久化对象管理器写入或者读取 ACC_SYNTHETIC 0x1000 声明synthetic,不存在源码中,表示编译器生成 ACC_ENUM 0x4000 声明为enum的元素 name_index
指向一个CONSTANT_Utf8_info结构的索引,代表一个有效的非全限定名(方法、字段、局部变量和形式参数的名称存储为非限定名称)
descriptor_index
指向一个CONSTANT_Utf8_info结构的索引,代表一个有效的字段描述符
attributes_count
表示该字段附加属性的数量
attributes[]
表示该字段附加属性列表,每一个项都是一个attribute_info结构,具体见后面attributes部分
methods_count
methods_count项的值给出了method_info结构在方法表中的数量
methods[]
method_info结构表示该类或接口类型声明的所有方法,包括实例方法、类方法、实例初始化方法和任何类或接口初始化方法;不包括从父类或者父接口继承的方法。
method_info结构
每个方法都是有method_info结构描述,一个类文件中的两个方法不能有相同的名称和描述符
方法描述符
MethodDescriptor:
( {ParameterDescriptor} ) ReturnDescriptor
ParameterDescriptor:
FieldType
ReturnDescriptor:
FieldType
VoidDescriptorVoidDescriptor:
v(表示方法不返回任何值)
方法:Object m(int i, double d, Thread t) {…}
方法描述符:(IDLjava/lang/Thread;)Ljava/lang/Object;
Method_info结构:
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }
access_flags:
表示方法的访问权限和属性,具体如下表:
名称 值 解释 ACC_PUBLIC 0x0001 声明public,可以包外访问 ACC_PRIVATE 0x0002 声明private,只有类内能访问 ACC_PROTECTED 0x0004 声明protected,子类可以访问 ACC_STATIC 0x0008 声明static ACC_FINAL 0x0010 声明final,不能被重写 ACC_SYNCHRONIZED 0x0020 声明synchronized,使用时被monitor包裹 ACC_BRIDGE 0x0040 声明一个桥接方法,由编译器生产 ACC_VARARGS 0x0080 声明具有可变数量的参数。 ACC_NATIVE 0x0100 声明native,用除了java的其他语言实现 ACC_ABSTRACT 0x0400 声明为abstract;没有提供任何实现。 ACC_STRICT 0x0800 声明strictfp,浮点模式是FP-strict ACC_SYNTHETIC 0x1000 声明synthetic,不存在源码中,表示编译器生成 name_index
指向一个CONSTANT_Utf8_info结构的索引,代表一个有效的非全限定名(方法、字段、局部变量和形式参数的名称存储为非限定名称)
descriptor_index
指向一个CONSTANT_Utf8_info结构的索引,代表一个有效的字段描述符
attributes_count
表示该方法附加属性的数量
attributes[]
表示该字段附加属性列表,每一个项都是一个attribute_info结构,具体见后面attributes部分
attributes_count
attributes数量
attributes[]
attribute_info集合,在Class文件,字段表,方法表中都可以携带自己的属性表集合,用以描述某些场景的专用信息,jdk8中一共有23项属性,具体如下:
属性名 使用位置 出现版本 解释 ConstantValue 字段表 1.0.2 final关键字定义的常量值 Code 方法表 1.0.2 Java方法包裹代码编译成的字节码指令,native方法没有 StackMapTable Code属性 6 在类型检查的验证中使用 Exceptions 方法表 1.0.2 Exceptions属性指示方法可能抛出哪些已检查异常 InnerClasses 类文件 1.1 内部类列表 EnclosingMethod 类文件 5.0 当且仅当类表示局部类或匿名类时,类必须具有EnclosingMethod属性 Synthetic 类、方法表、字段表 1.1 标识方法或字段为编译器自动生成 Signature 类、方法表、字段表 5.0 支持泛型下的方法签名,任何类、接口、初始化方法或者成员的泛型签名如果包含类型变量(Type Variables)或者参数化类型(Parameterized Type),该属性记录泛型签名信息 SourceFile 类文件 1.0.2 记录源文件名称 SourceDebugExtension 类文件 5.0 存储额外的调试信息 LineNumberTable Code属性 1.0.2 Java源码行号和字节码指令对应关系 LocalVariableTable Code属性 1.0.2 方法的局部变量描述,调试期间用于确定局部变量的值 LocalVariableTypeTable Code属性 5.0 在方法执行期间,调试器可以使用它来确定给定局部变量的值 Deprecated 类、方法表、字段表 1.1 标记类、接口、方法、字段被废弃 RuntimeVisibleAnnotations 类、方法表、字段表 5.0 运行时可见注解 RuntimeInvisibleAnnotations 类、方法表、字段表 5.0 运行时不可见注解 RuntimeVisibleParameterAnnotations 方法表 5.0 运行时可见方法参数注解 RuntimeInvisibleParameterAnnotations 方法表 5.0 运行时不可见方法参数注解 RuntimeVisibleTypeAnnotations 类、方法表、字段表、Code属性 8 运行时可见类型注解 RuntimeInvisibleTypeAnnotations 类、方法表、字段表、Code属性 8 运行时不可见类型注解 AnnotationDefault 方法表 5.0 记录注解类元素的默认值 BootstrapMethods 类文件 7 保存invokedynamic指令引用的引导方法说明符 MethodParameters 方法表 8 记录了关于方法的形式参数的信息,比如它们的名称。
以上,就是本次的全部内容。