深入虚拟机笔记之class文件

第6章 java class文件

    java class文件是对java程序二进制文件格式的精确定义。每一个class文件都对一个java类或接口做出了全面的描述。一个class文件中只能包含一个类或者接口。

    java class文件是8位字节的二进制流。数据项按顺序存储在class文件中,相邻的项之间没有任何间隔,占据多个字节空间的项按高位在前的顺序分为几个连续的字节存放。

    java class文件能够包含许多不同大小的项,在class文件中,可变长度项的大小和长度位于其实际数据之前,这个特性使得class文件流可以从头到尾被顺序解析。

    java class文件中包含了java虚拟机所需知道的,关于类或接口的所有信息。

   

    java class文件基本类型: 所有存储在u2、u4、u8项中的值,在class文件中以高位在前的形式出现。

        u1(1字节无符号类型)、 u2(2字节无符号类型)、

        u4(4字节无符号类型)、 u8(8字节无符号类型)。

 

 

    magic(u4):每个java class文件的前4个字节被称为它的魔数(magic number):0xCAFEBABE。魔数的作用在于分辨java class文件和非java class文件。

    minor_version(u2)和major_version(u2):接下来的4个字节分别定义了主、次版本号。随着java计数的发展,java class文件格式可能会加入新的特性,文件格式一旦发生变化,版本号也会随之变化。对于java虚拟机来说,版本号确定了特定的class文件格式,通常只有给定主版本号和一系列次版本号后,java虚拟机才能够读取class文件。如果class文件的版本号超出了java虚拟机所能处理的有效范围,java虚拟机将不会处理该class文件。

    constant_pool_count(u2)和constant_pool(cp_info):版本号后面是常量池,常量池包含了与文件中类和接口相关的常量,存储了诸如字符串、final变量值、类名、方法名的常量。java虚拟机把常量池组织为入口列表的形式,在实际列表constant_pool之前,是2个字节的constant_pool_count计数。常量池中的许多入口都指向其他的常量池入口。在整个class文件中,指示常量池入口在常量池列表中位置的整数索引(如#10)都指向这些常量池入口。列表中的第一项索引值为1,尽管没有索引值为0的入口,但缺失的这一个入口也被constant_pool_count计数在内(count值为常量池入口总数+1)。

    每个常量池入口都从一个长度为1个字节的标志开始,这个标志指出了列表中该位置的常量类型。在动态连接的java程序中,常量池充当了十分重要的角色。



 

    除了字面常量值以外,常量池还可以容纳以下几种符号引用:

    (1) 类和接口的全限定名。

    (2) 字段的名称和描述符。字段描述符是指示字段的类型的字符串。

    (3) 方法的名称和描述符。方法描述符也是字符串,该字符串指示方法的返回值和参数数量、顺序和类型。

    在运行时,java虚拟机使用常量池中的全限定名、方法和字段的描述符,把当前类或接口中的代码与其他类或接口中的代码连接起来。由于class文件并不包含其内部组件最终内存布局的信息,因此类、字段和方法不能被class文件中的字节码直接引用。java虚拟机从常量池中获得符号引用,然后在运行时解析引用项的实际地址。

   

    access_flags(u2):紧接常量池后的两个字节称为access_flags,它展示了文件中定义的类或接口的访问标志信息。访问标志指明文件中定义的是类还是接口,以及在类或接口的声明中使用了哪种修饰符:public(类和接口)、final(类)、super(类和接口)、interface(接口)、abstract(类和接口)。

    this_class(u2):是一个2字节的常量池索引。在this_class位置的常量池入口必须为CONSTANT_Class_info表。该表由两个部分组成:tag和name_index。标签是一个具有CONSTANT_Class值的常量,name_index的常量池入口为一个包含了类或者接口全限定名的CONSTANT_Utf8_info表。

    super_class(u2):是一个2字节的常量池索引,在 super_class位置的常量池入口是一个指向该类超类全限定名的CONSTANT_Class_info表。除了Object类外,super_class对于所有的类均有效。对Object类,super_class 的值为0。对于接口,super_class的值为java.lang.Object。

    interface_count(u2)和interfaces:在文件中由该类直接实现或者接口扩展的父接口的数量。在计数后面是名为interfaces的数组,它包含了父接口的常量池索引。每个父接口使用常量池中的CONSTANT_Class_info入口来描述,指向接口的全限定名。

    fields_count(u2)和fields:对该类或者接口中所声明的字段的描述。fields_count是类变量和实例变量的数量总和。在计数后面是不同长度的field_info表的序列。在fields列表中不列出从超类或者父接口中继承而来的字段。fiels列表可能会包含在对应的java源文件中没有定义的字段,这是因为编译器可能会在编译时向类或者接口添加字段。比如,一个内部类的fields列表为了保持对外围类实例的引用,java编译器会为内部类fields列表添加外围类类型的实例变量,这种字段使用Synthetic属性标识。每个field_info表都展示一个字段的信息,包含字段的名字、描述符和修饰符;如果被声明为final,field_info还会展示其常量值或者指向其在常量池中的索引。

    method_count(u2)和methods:对该类或者接口中所声明的方法的描述。method_count是2个字节的所有方法的计数,只计算该类或者接口显式声明的方法(继承来的方法不被计算)。method_info表包含与方法相关的信息,包括方法名和描述符(返回值类型和参数类型)。如果方法既不是abstract,又不是native(本地的),那么method_info表就包含方法局部变量所需的栈空间长度、为方法所捕获的异常表、字节码序列以及可选的行数和局部变量表。

    attributes_count(u2)和attributes:class文件最后的部分是属性,它给出了在class文件中类或接口所定义的属性的基本信息。每个attribute_info的第一项是指向常量池中的CONSTANT_Utf8_info表的索引。java虚拟机实现定义了两种属性:SourceCode和InnerClasses。

    特殊字符串:常量池中容纳的符号引用包括三种特殊的字符串(全限定名、简单名称和描述符)。

    全限定名:当常量池入口指向类或接口时,它给出该类或者接口的全限定名(点用斜线取代)。

    简单名称:字段名和方法名以简单名称形式出现在常量池入口中。比如toString。

    描述符:字段的描述符给出字段的类型;方法的描述符给出了方法的返回值类型和方法参数的数量、类型

                  以及顺序。V终结符表示方法返回值为void,对象终结符L和分号(;),数组终结符[,方法描述符终

                  结符左右括号(),8中基本类型终结符B=byte、C=char、D=double、F=float、I=int、J=long、

                  S=short、Z=boolean。实例方法的描述符没有包含作为第一个参数被传给实例方法的隐藏this

                  参数,但所有调用实例方法的java虚拟机指令都会隐式传递this参数(不会传给类方法)。

 

    

    常量池:是一个可变长度的cp_info表的有序序列。cp_info表中的tag项是一个无符号的byte类型值,它表明了表的类型和格式。info项存放对应的类型表。cp_info表一共有11种类型。

    CONSTANT_Utf8_info表:可变长度的utf8表使用一种ut8格式的变体来存储一个常量字符串。可存储:

        文本字符串、类或接口的全限定名、字段/方法的简单名称和描述符、与属性相关的字符串。utf-8编码

        模式允许字符串中的所有Unicode字符以2个字节的形式表示,ascII字符(null除外)以1个字节表示。

        utf8表跟标准utf8格式的区别:bytes中的空字符null用2个字节表示,而且bytes项只使用了标准ut8编码

        的单字节、双字节和三字节编码。

 
    CONSTANT_Integer_info表:用来存储常量的值,该表不存储符号引用。bytes为4字节。

    CONSTANT_Float_info表:用来存储常量的值,该表不存储符号引用。bytes为4字节。

    CONSTANT_Long_info表:用来存储常量的值,该表不存储符号引用。bytes为8字节。

    CONSTANT_Double_info表:用来存储常量的值,该表不存储符号引用。bytes为8字节。

    CONSTANT_Class_info表:使用符号引用来描述类或者接口。无论指向类、接口、字段还是方法,所有

        符号引用都包含一个CONSTANT_Class_info表。name_index给出了包含类或接口的全限定名的CONSTANT_Utf8_info表。

 

    CONSTANT_String_info表:固定长度的CONSTANT_String_info表用来存储文本字符串值,该值也可表

        示为类java.lang.String的实例。该表不存储符号引用。string_index给出了包含文本字符串值的

        CONSTANT_Utf8_info入口的索引。

    CONSTANT_Fieldref_info表:固定长度的CONSTANT_Fieldref_info表描述了指向字段的符号引用。

        class_index给出了声明被引用字段的类或接口的CONSTANT_Class_info表入口的索引。class文件可以

        包含它使用的任何静态final字段的常量值得拷贝。如果引用的字段被初始化为编译时的常量,该类将会

        在它自己的常量池中拥有一个 CONSTANT_基本类型_info表;但是如果引用的字段使用运行时才能计

        算出来的表达式来初始化静态final字段,那么使用该字段的类的常量池中,将会有一个对该引用类的

        字段进行符号引用的CONSTANT_Fieldref_info表。name_and_type_index提供了

        CONSTANT_NameAndType_info表入口的索引,该入口提供了字段的简单名称和描述符。

 

    CONSTANT_Methodref_info表:固定长度的 CONSTANT_Fieldref_info表使用符号引用来表述类中声明

        的方法(不包括接口中的方法)。

    CONSTANT_InterfaceMethodref_info表:固定长度的 CONSTANT_InterfaceMethodref_info表使用符号引

        用来描述接口中声明的方法(不包括类中的方法)。

    CONSTANT_NameAndType_info表: 固定长度的CONSTANT_NameAndType_info表构成指向字段或者

       方法的符号引用的一部分。该表提供了所引用字段或方法的简单名称和描述符的常量池入口。

  

     字段:在类或接口中声明的每一个字段(类变量或者实例变量)都有class文件中的一个名为field_info的可变长度的表进行描述。在一个class文件中,不存在两个具有相同名字和描述的字段。

 

     方法:在class文件中,每个在类和接口中声明的方法,或者由编译器产生的方法,都有一个可变藏毒的

method_info表来描述。同一个类中不能存在两个名字及描述符完全相同的方法。在java语言中,同一个类或接口中声明的两个方法不能有同样的签名(除返回类型之外的描述符);但在class文件中,两个方法可以拥有同样的签名(前提是,返回值类型不同)。有可能在class文件中出现的编译器产生的方法:实例初始化方法(名为<init>)用来执行实例变量初始化、实例初始化代码块和构造器代码;类与接口初始化方法(名为<clinit>)用来执行类变量初始化、静态初始化代码块。

 

    属性:属性在class文件中多次出现,它可以出现在class文件、field_info、method_info和Code_attribute表中。java虚拟机规范定了了9种属性。为了正确解释java class文件,java虚拟机实现都必须能够识别三种属性:Code、ConstantValue和Exception。为了正确实现java和java2平台的类库,虚拟机实现必须能够识别InnerClasses和Synthetic属性。但可以自主选择究竟是识别还是忽略其他的一些预定义的属性(Duprecated等)。



 

猜你喜欢

转载自jaesonchen.iteye.com/blog/2288537
今日推荐