yaochow@home:~$

【介绍】Java ClassFile Structure

一个编译后的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 u4

魔数,固定值为0xCAFEBABE。作用是确定此Class文件是否可以被虚拟机所接受。

minor_version u2、magor_version u2

副版本号、主版本号 00 00 00 33 代表副版本号是0,主版本号51 JDK1.7。 Class文件能够被相同或高版本加载,不能被低版本加载。jdk中版本号是从45开始的,所以51代表的是1.7版本。

constant_pool_count u2、cp_info:constant_pool[constant_pool_count -1]

常量池计数器,因为常量池中的数据不是固定的,所以需要一个计数器。 计数器是u2(2个byte),所以constant_pool_count不能超过16的四次幂-1,即65535。 常量池计数器默认从1开始,而不是0,即当constant_pool_count = 1时,常量池中cp_info的个数为0;当cp_info的个数为n时,常量池中的cp_info个数为n-1. 在指定Class文件规范的时候,将索引#0项常量空出来是有特殊考虑的,使用索引#0表达“不引用任何一个常量池项”的意思。

constant_pool中主要存放着两种常量,字面量和符号引用。

  • 字面量:文本字符、声明为final的常量值,基础数据类型的值。
  • 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
    • 全限定名中的“.”会被“/”替代,存储进常量池中。
    • 符号引用&直接引用:符号引用在javac编译时产生,完全符合Java虚拟机规范;编译产生Class文件,而Class文件此时还没有被JVM加载,此时的符号引用仅仅是一个标识,符号引用并不知道其对应的数据放在哪一处内存;当JVM加载Class文件时,会将具体的信息提取出来放入内存,进而就知道了哪一条信息处于哪一处内存,由于符号引用完全符合java虚拟机规范,所以这时根据每一处信息的内容,逆推出该信息对应的符号引用,然后再使用该信息的实际的内存地址来替代符号引用。

cp_info又可以分为14种结构类型:

类型 tag (u1) 说明
CONSTANT_Utf8_info 1 utf-8编码的字符串占用的字节数length u2|字符串 length
CONSTANT_Integer_info 3 按照高位在前存储的int值 u4
CONSTANT_Float_info 4 按照高位在前存储的float值 u4
CONSTANT_Long_info 5 按照高位在前存储的long值 high_bytes u4 low_types u4
CONSTANT_Long_info 6 按照高位在前存储的double值 high_bytes u4 low_types u4
CONSTANT_Class_info 7 index(指向全限定名常量项的索引) u2
CONSTANT_String_info 8 index(指向字符串字面量的索引的索引) u2
CONSTANT_Fieldref_info 9 index(指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项) u2,index(指向字段描述符CONSTANT_NameAndType_info的索引项) u2
CONSTANT_Methodref_info 10 index(指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项) u2,index(指向字段描述符CONSTANT_NameAndType_info的索引项) u2
CONSTANT_InterfaceMethodref_info 11 index(指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项) u2,index(指向字段描述符CONSTANT_NameAndType_info的索引项) u2
CONSTANT_NameAndType_info 12 index(指向该字段活方法名称常量项的索引) u2,index(指向该字段或方法描述符的索引) u2
CONSTANT_MethodHandle_info 15 reference_kind(值必须在[1,9]之间,它决定了方法句柄的类型,方法句柄类型的值表示方法句柄的字节码行为) u2,reference_index(值必须是对常量池的有效引用) u2
CONSTANT_MethodType_info 16 descriptor_index(值必须是对常量池的有效引用,常量池在索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符) u2
CONSTANT_InvokeDynamic_info 18 bootstrap_method_attr_index(值必须是对当前Class文件中引导方法表的bootstrap methods[]数据的有效索引) u2, name_and_type_index(值必须是对当前常量池的有效索引,常量池在该处的索引必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符)

access_flags u2

类型 说明
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否声明final
ACC_SUPER 0x0020 是否允许使用invokespicial字节码指令的新语意。
ACC_INTERFACE 0x0200 是否为接口
ACC_ABSTRACT 0x0400 是否为抽象类
ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码生成访问标志

多个标识符access_flags的值累加。

this_class u2

类索引,确定当前类的全限定名

super_class u2

父类索引,确定当前类的父类全限定名

interfaces_count, interfaces[interfaces_count]

接口索引计数器、接口索引集合

fields_count u2|field_info:fields[fields_count]

field_info用于描述接口或者类中声明的变量。字段包括静态变量和实例变量,不包括方法内部的局部变量。 field_info的结构为:access_flags u2|name_index u2|descriptor_index u2|attributes_count u2|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_VOLATILE 0x0040 字段是否volatile
ACC_TRANSIENT 0x0080 字段是否transient
ACC_SYNTHETIC 0x1000 字段是否由编译器自动产生
ACC_ENUM 0x4000 字段是否enum
name_index

field的自定义名称,字段名

descriptor_index

字段类型的描述

类型 说明
B 基本类型byte
C CHAR
D double
F float
I int
J long
S short
Z boolean
V void
L + 类全限定名 + “;” 比如:Ljava/lang/String;

数组类型的描述,每一维度需要在前面加一个“[”;如:String[][],将被记录为“[[java/lang/String;”,int[]将被记录为“[I”

methods_count u2,method_info:methods[methods_count]

method_info结构与field_info是一样的。

attributes_count,attribute_info:attributes[attributes_count]

attribute_name_index u2 attribute_length u4 info attribute_length

Class文件、字段表、方法表都可以携带自己的属性表集合,以描述某些场景专有的信息。attributes也可以嵌套attributes。

类型 说明
Code 方法表中 Java代码编译成的字节码指令
ConstantValue 字段表中 final关键字定义的常量值
Deprecated 类、方法表、字段表 被声明为deprecated的方法和字段
Exceptions 方法表中 方法声明的异常
LocalVariableTable Code属性中 方法的局部变量描述
LocalVariableTypeTable 类中 DK1.5中新增的属性,它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
innerClasses 类中 内部类列表
EnclosingMethod 类中 仅当一个类为局部类或者匿名类时,才能拥有这个属性,这个属性用于表示这个类所在的外围方法
LineNumberTable Code属性中 Java源码的行号与字节码指令的对应关系
StackMapTable Code属性中 JDK1.6中新增的属性,供新的类型检查验证器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
Signature 类中、方法表中、字段表中 DK1.5新增的属性,这个属性用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息
SourceFile 类中 记录源文件名称
SourceDebugExtension 类中 JDK1.6中新增的属性,SourceDebugExtension用于存储额外的调试信息。如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码运行在Java虚拟机汇中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension就可以存储这些调试信息。
Synthetic 类中、方法表中、字段表中 标识方法或字段为编译器自动产生的
RuntimeVisibleAnnotations 类中、方法表中、字段表中 JDK1.5中新增的属性,为动态注解提供支持。RuntimeVisibleAnnotations属性,用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的。
RuntimeInvisibleAnnotations 类中、方法表中、字段表中 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations相反用于指明哪些注解是运行时不可见的。
RuntimeVisibleParameterAnnotations 方法表中 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations类似,只不过作用对象为方法的参数。
RuntimeInvisibleParameterAnnotations 方法表中 JDK1.5中新增的属性,作用与RuntimeInvisibleAnnotations类似,只不过作用对象为方法的参数。
AnnotationDefault 方法表中 JDK1.5中新增的属性,用于记录注解类元素的默认值
BootstrapMethods 类中 JDK1.7新增的属性,用于保存invokedynamic指令引用的引导方法限定符

总结

java文件编译后生成class文件,class文件按照固定的规则依次表示不同的功能数据供JVM识别。 JVM通过解析各项表示不同功能的信息进行对象的生成,方法的调用等等。

以上,看完后相信对类加载会有更深层次的理解。