Java Class 文件是什么
JVM Spec (Second Edtion) 中有表述: Java Class 文件由一个 8 位字节流组成,所有的 16 位、 32 位和 64 位数据分别通过读入 2 个、 4 个和 8 个字节来构造,多字节数据总是按照 Big-endian 顺序来存放,即高位字节放在低地址处。每个 Class 文件都包含且仅包含一个 Java 类型 ( 类或者接口 )
或许,《 JVM Spec 》中的表述不够明确,那么我们可以参考一下《 Inside JVM (Second Edtion) 中的表述: Java Class 文件特指以 .Class 为后缀名的 Java 虚拟机可装载的文件。这个表述应该比较明确了,如果还是觉得不够明确,我只能建议你读完这篇文章之后再回头来理解看看了 J
二、 Java Class文件的格式
JVM Spec (Second Edtion) 中指出,Java Class 文件格式用一个类似于C 结构的记号编写的伪结构来表示。其中,描述Java Class 文件格式结构的内容称为项(items) 。项的大小是可变的,每个项按顺序存储在Class 文件中,相邻的项之间没有任何间隔,这样可以使Class 文件紧凑。那么,这个项具体指什么呢?请看《JVM Spec 》中给出的ClassFile 结构的图示:
该图按顺序地列出了 ClassFile 的项,每一个项都包括其类型和名称。项的类型可能是u1u2u4或者一个表名。其中u1u2u4Java Class文件的基本数据类型,分别表示一个无符号类型的、占124个字节的数据。例如“u4 magic;”表示一个了ClassFile项,该项的名称为magic,即魔数,该项共占4个字节,这无符号类型的、4个字节的二进制数的具体值就是该magic项的具体内容。当的项类型为一个表名时,可以根据表名去查阅相关表,这些表的项也都是由Java Class文件的基本数据类型以及项的名称构成。ClassFile结构中的各项可以简要分析如下:
ClassFile表结构
     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];
     }
(1) magic( 魔数)
每个Class 文件的前4 个字节被称为它的魔数(magic number : 0xCAFEBABE 。魔数的作用在于:可以轻松地分辩出Java Class 文件和Java Class 文件。(如果一个文件不是以0xCAFEBABE 开头,它就肯定不是Java Class 文件)。当Java 还称为“Oak ”的时候,这个魔数就已经定下来了,它预示了Java 这个名字的出现。魔数的来历请大家自己查阅 J
(2) minor_version major_version
Class 文件的下面4 个字节包含了主、次版本号。通常只有给定主版本号和一系列次版本号后,Java 虚拟机才能够读取Class 文件。如果Class 文件的版本号超出了Java 虚拟机所能够处理的有效范围,Java 虚拟机将不会处理该Class 文件。
(3) constant_pool_count
版本号后面的项是constant_pool_count 即常量池计数项,该项的值必须大于零,它给出Class 文件中constant_pool 项的个数,包括索引为0 constant_pool 项,但是该项不出现在Class 文件的constant_pool 列表constant_pool [ ] 中。
(4) constant_pool [ ]
constant_pool_count 项下面是constant_pool [ ] 项。常量池由constant_pool_count-1 个连续的、可变长度的cp_info 表构成,其中存储了诸如文字字符串、final 变量值、类名和方法名的常量。
cp_info的通常形式
cp_info {

         u1 tag;

         u1 info[];

    }

常量池constant_pool 的第一个表项constant_pool [0 ] 并不出现在Class 文件中,它被保留为Java 虚拟机内部实现使用。索引为1 constant_pool_count-1 的每一个constant_pool 表项都是一个变长结构,其通常格式如上表cp_info 所示。cp_info 表的tag 项是一个无符号的byte 类型值,它表明了cp_info 表的类型和格式,具体的cp_info tag 类型如下表;
  
cp_info 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
需要说明的是,cp_info 只是一个抽象的概念,在Class 文件中,它表现为一系列具体的、形如CONSTANT_Xxxx_info constant_pool 结构,其具体的格式由cp_info 表的tag 项(即每个常量池的第一个字节)来确定。例如,若constant_pool_count 项后面一个字节的值为7 ,即第一个cp_info 结构的tag 项的值为7 ,它表示该constant_pool 是一个CONSTANT_Class_info 结构的常量池,因此onstant_pool[ ] 数组项(即constant_pool 列表)的第一个项就是CONSTANT_Class_info 结构的常量池,该常量池的具体长度可以根据其表结构的各项(项的类型都是基本类型)的长度和来确定。CONSTANT_Class_info 表的结构请查阅《JVM Spec (2nded) 的第四章或者《Inside JVM (2nded) 的第六章;如果紧接着这个常量池的一个字节的值为1 ,则下一个常量池就是CONSTANT_Utf8_info 结构的常量池,其长度的计算与前一个是类似的。
cp_info 表我们可以知道,若cp_info 表中tag( 标记) 项的值为1 时,我们就应该去查阅与之相对应的CONSTANT_Utf8_info 表,若cp_info 表中tag( 标记) 项的值为3 时,我们就应该去查阅与之相对应的CONSTANT_Integer_info 表,其它的类推。这些表可以查阅《JVM Spec (2nded) 的第四章或者《Inside JVM (2nded) 的第六章。
(5) access_flegs
紧接常量池后的两个字节称为 access_flags ,它的值是 Java 类型声明中使用的修饰符的掩码。 access_flags 修饰符如下表所示:
access_flags 修饰符
标志名称                         含义
ACC_PUBLIC      0x0001 声明为public ,可以从它的包外访问
ACC_FINAL       0x0010 声明为final ,不允许有子类
ACC_SUPER       0x0020 invokespecial 指令处理超类的方法
ACC_INTERFACE 0x0200 表明是一个接口,而不是一个类
ACC_ABSTRACT   0x0400 声明为abstract ,不能被实例化
access_flags 项展示了文件中定义的类或接口的一些信息。例如,访问标志指明文件中定义的是类还是接口;访问标志还定义了在类或接口的声明中,使用了哪些修饰符;类和接口是抽象的还是公共的等等。
(6) this_class
接下来的两个字节为this_class , 它是一个对常量池的索引。在this_class 位置的常量池入口必须为CONSTANT_Class_info 表。
(7) super_class
Class 文件中紧接在this_class 之后的两个字节是super_class 项,super_class 项的值必须为0 ,或者是对常量池的一个有效索引。如果super_class 项的值为0 ,则该Class 文件必须表示java.lang.Object 类。如果super_class 项的值不为0 ,则又分为两种情况,若该Class 文件表示一个类,则super_class 项必须是对常量池中该类的超类的CONSTANT_Class_info 结构的索引,这个超类和它的任何超类都不能是一个final 类;若该Class 文件表示一个接口,则super_class 项必须是对常量池中表示java.lang.Object 类的一个CONSTANT_Class_info 结构的索引。
(8) interfaces_count interfaces [ ]
紧接着super_class 项后面的两个字节是interfaces_count 项,此项表示由该类直接实现或者由该接口所扩展的超接口的数量。
紧接着interfaces_count 项后面的是interfaces 数组项,它包含了由该类直接实现或者由该接口所扩展的超接口的常量池索引,共计interfaces_count 个索引。interfaces 数组中的常量池索引按照该类型在源代码中给定的从左到右的顺序排列。
(9) fields_count fields [ ]
Class 文件中,紧接在interfaces [ ] 后面的是对在该类或者接口中所声明的字段的描述。fields_count 项的值给出了fields 表中的field_info 结构的个数,它表示该Java 类型声明的类变量和实例变量的数量的总和。
每个field_info 表都表示了一个字段的信息,包括该字段的名字、描述符、和修饰符等。这些信息有的放在field_info 表中,如修饰符;有的则放在field_info 表所指向的常量池中,如名字和描述符。
需要说明的是,只有在该Java 类型中声明的字段才能在fields 列表中列出,fields 列表中不包括从超类或者超接口中继承而来的字段。
(10) methods_count methods [ ]
Class 文件中,紧接着fields 后面的是对在该类或者接口中所声明的方法的描述。首先是methods_count 项,它占两个字节长度,它的值表示对该类或者接口中声明的所有方法的总计数。methods_count 项后面是methods 数组,它由methods_count 个连续的method_info 表构成。
每个method_info 表都包含了与方法相关的一些信息,包括方法名和描述符(即方法的返回值及参数类型)。如果一个方法既非abstract 也非native ,那么method_info 表则包含方法局部变量所需的栈空间长度、为方法所捕获的异常表、字节码序列以及可选的行号表和局部变量表等信息。
需要说明的是,只有在该Java 类型中显式定义的方法才能在fields 列表中列出,fields 列表中不包括从超类或者超接口中继承而来的方法。
(11) attributes_count attributes [ ]
Class 文件中最后的部分是属性(attribute , 它给出了在该文件中类或者接口所定义的属性的基本信息。首先是attributes _count 项,它占两个字节长度,它的值表示出现在后续列表attributes 数组中的attributes _info 表的总计数。attributes _count 项后面是attributes 数组,它由attributes _count 个连续的attributes _info 表构成。每个attributes _info 表的第一项是指向常量池中CONSTANT_Utf8_info 表的索引,该表给出了此属性的名称。在《JVM Spec (Second Edtion) 中为ClassFile 表定义的唯一属性是SourceFile 属性。
需要说明的是,属性有很多种,在Class 文件中的很多地方都出现了属性这一项,在顶层ClassFile 表中有attributes 属性项,在field_info 表中也有attributes 属性项,在method_info 中也有attributes 属性项,但是它们各有各的功能,详见上述分析。
从上面的分析可以看出,Class 文件中的各项是按顺序存储的,因此Class 文件可以看作一个线性数据流,它可以从头到尾地被顺序解析为各个项,首先读出项的大小,然后读出项的数据。详细分析下一篇blog----一个Java Class文件的 实例解析。