类文件结构

本文从以下方面描述类文件结构:

(一)class文件的结构

一、魔数与class文件版本

二、常量池

三、访问标志

四、类索引、父类索引、接口索引集合

五、字段表集合

六、方法表集合

七、属性表集合

(一)class文件的结构

1、概述

            class文件是以8位字节为基本单位二进制流,各个结构严格按照顺序排列起来,中间没有任何分割符。  当遇到要占用8位字节以上的空间数据时,则会按照高位在前的方式分割成若干个字节进行存储。

2、class文件中的数据类型

 Java虚拟机规范规定,Class文件的格式采用一种类似C语言的结构体的伪结构存储。

 有两种数据类型:

 (1)无符号数

 (2)表

2.1、无符号数

         属于基本数据类型,以u1,u2,u4,u8分别表示1字节,2字节,4字节,8字节的无符号数;

         无符号数用来描述:数字,索引引用,数量值,或者按照UTF-8编码的字符串值。

2.2、表

         表是有多个无符号数,或者其他表作为数据项构成的复合数据类型,所有表习惯以“_info”结尾;

2.3、图示class文件格式


 一、魔数与class文件版本

1、概述

             class文件的开始4字节表示为魔数(Magic Number)。开始四字节值为0xCAFEBABE。

2、作用

            用于确定该文件是否是一个能被虚拟机加载的Class文件。

 3、次版本、主版本

            紧随魔术之后的u2为次版本(minor_version),之后的u2两个字节为主版本(major_version)



 

4、class文件的版本号



 

二、常量池

 1、概述

              在次版本、主版本之后的数据项为常量池,常量池与class文件中其他项目关联最为密切,也是占用class文件最大的数据项之一,也是class文件中第一个出现表结构的数据。

2、常量池池容量计数值(constant_pool_count)

      常量池的入口放置一个u2类型的数据,代表常量池容量计数值(constant_pool_count)。池容量计数值是从1开始的,0项有特殊意义(0项,为了满足某些指向常量池的索引值的数据在特定的情况下需要表达“不引用任何一个常量池项目”)。

2.1、注意             

        Class文件中只有常量池是从1开始计数,其他集合类型,诸如:接口索引集合、字段表集合、方法表集合、等计数从0开始。

3、常量池存放的常量类型

(1)字面量(Literal)

(2)符号引用(Symbolic References)

3.1、字面量

        类似于java中的常量,如:文本字符串、被声明为final的常量值。

3.2、符号引用

         属于编译原理方面的概念,包括如下3类常量:

       (1)类和接口的全限定名(Full Qualified Name)

       (2)字段名称和描述符

       (3)方法名称和描述符

3.2.1 全限定名、简单名称、描述符

(1)全限定名:包名与类名组成的字符串

(2)简单名称:没有类型和参数修饰符的方法或字段名称

(3)描述符:

         ① 用于描述:字段的数据类型,方法的参数列表(包括参数类型,数量,顺序)以及方法返回值;

         ②描述符规则:8个基本数据类型以及无返回值的void都用一个大写字母表示,见下图。对于对象类型来说用字符大写L加对象的全限定名表示,如:Ljava/lang/String;

         ③数组表示:一维数组如:int[]表示为[I,二维数组java.lang.String[][]表示为[[Ljava.lang.String

         ④描述符用于描述方法参数时,按照先参数列表,后返回值的顺序描述。如方法void fun(),表示为

()V,java.lang.String toString(),表示为 ()Ljava.lang.String

   

3.2.2、描述符表示含义图



 

 4、常量池中的项目类型

       常量池的每一项常量都是一张表,共有11项目数据各不相同的表结构数据,11中表结构数据都有一个共同点,开始位置都是u1类型的标志位(tag,取值1-12,缺少标志2数据类型),tag标志位表示当前的常量属于那种常量类型,11中常量类型的含义见下图:


 4.1、11中数据类型结构总表

 

 

 三、访问标志(access_flags)

       

1、概述:

      紧随常量池之后的u2类型的数据代表访问标志(access_flags)。

       

2、作用:

      识别类或接口的访问信息,包括,class为类或接口,是否定义为public、是否定义为abstract;如果为类是否声明为final。

3、访问表示的含义表



 
4、说明

      access_flag共有32个标志位可以使用,当前只定义了8个,没有用到的标志位都为0。

      例如:用到了 ACC_PUBLIC(0x0001)以及ACC_SUPER(0x0020), 0x0001 | 0x0020 = 0x0021(呈现在字节码中文件的16进制数),所以其他几项标志位都为0。  

   

四、类索引、父类索引、接口索引集合

1、概述

            类索引(this_class)和父类索引(super_class)都是一个u2的数据类型,而接口索引集合(interfaces)是一组u2类型的数据集合,class文件通过上述数据类型确定继承关系。这些类型的数据按顺序排列在访问标志(access_flags)之后。

1.1、类索引(this_class)

            用于确定类的全限定名。

1.2、父类索引(super_class)

            用于确定这个类的父类的全限定名。java不支持多继承,除了java.lang.Object之外,所有的类都有父类,所以父类索引不为0。

1.3、接口索引集合(interfaces)

            用于描述类实现了那些接口,这些实现的接口(如果类本身是接口,则为多继承extends)将按照implements语句后的接口顺序从左到右排列在接口索引集合中。

2、说明

(1)类索引(this_class)和父类索引(super_class)引用u2类型的索引值表示,它们指向一个类型为CONSTANTS_Class_info的描述符常量,通过CONSTANTS_Class_info类型中的常量索引值可以找到定义在CONSTANTS_Utf8_info类型的常量中的全限定名字符串。

(2)接口索引集合,第一项为一个u2类型的接口计数器(interfaces_count),表示索引表的容量。

 五、字段表集合(fields) 

1、概述

      字段表(fields_info)用于描述类或接口中的变量。

      字段(fields)包括,类变量和实例变量,不包括方法内部声明的变量。

     

1.1、描述一个字段包括的信息:

       ①字段的作用域(public 、private、protected);

       ②类变量后者是实例变量(static);

       ③是否为常量final;

       ④并发可见性volatile是否强制从内存读写;

       ⑤可否序列化(transient);

       ⑥字段的数据类型(基本数据类型、数组、对象)

 1.2、对于字段的修饰符都是boolean值,适合使用标志位来表示,对于字段的数据类型、字段的名字不确定,只能应用常量池中的常量来表示。

2、字段表结构

 

 

字段访问标志(access)

 

 3、说明

 (1)紧随访问标志是两项索引值:name_index、description_index。都是对常量池的引用,分别代表着字段的简单名称和方法的描述符。(前面章节已经解释过描述符)。对于description_index之后的attribute_info信息,在后续的章节介绍。

 (2)字段表集合集合中不会列举出从父类中继承来的字段。但可能会出现代码中不存在的字段,比如内部类保持对外部类的访问,会自动添加指向外部类的实例字段。

(3)java中字段无法重载,字段名称不能重名。对于字节码而言,两个字段的描述符(描述字段的数据类型)不一致,字段名称相同是合法的。

 六、方法表集合(method_info)

1、方法表结构



 方法的访问标志(access_flags)


2、特征签名

      java代码中方法的特征签名只包括了方法的名称、参数顺序、参数的类型,而字节码文件中特征签名还包括方法的返回值及受查异常表。

3、说明

(1)volatile、transient关键字不能修饰方法,因此访问标志中没有上述两种标志。

(2)java方法中的代码,存放在属性表集合中一个名为“code”的属性里面,后续介绍属性表。

(3)如果父类的方法在子类中没有被重写,方法表集合中不会出现来自父类的方法信息(所见即所得,只针对该类文件中出现的方法进行编译)。同样,可能会出现编译器自动添加方法,典型的为类构造器“<clinit>"和实例构造器"<init>"方法

(4)Java中方法的重载,除了与原方法名称一样,还必须拥有一个与原方法不同的特征签名,返回值不会包括在特征签名之内,java中不能依靠返回值不同对方法重载。

(5)class文件中,两个方法同名,同特征签名,方法的返回值不同,可以共存于一个class文件中。

 七、属性表集合

         属性表(attribute_info):class文件中字段表,方法表可以带有自己的属性表集合,用于描述某些场景的专有信息。

虚拟机规范定义的属性:



 

 对于上述属性的名称,则需要从常量池中引用一个CONSTANT_Utf8_info类型的常量表示,而属性值的结构则是完全自定义的。

7.1 Code属性

      java方法体中的代码经过javac编译之后,最终的字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,并不是所有的方法都有该属性,比如说:接口,抽象类中的抽象方法。

1、code属性

code属性的结构:

 

 (1)attribute_name_index是指向CONSTANT_Utf8_info型的常量的索引,常量值固定为“Code”;

          attribute_length指示了属性值的长度,属性名+属性值=u6字节,所以整个code属性减去6字节为属性值的长度;

(2)max_stack:操作数栈(Oprand Stacks)深度的最大值。方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行时需要根据这个值来分配栈帧中操作数栈的深度。

(3)max_locals:代表局部变量所需的存储空间。max_locals单位是slot。

         slot是虚拟机为局部变量分配内存所使用的最小单位。1slot为32位。

(4)code,code_length:用于存储java源程序编译后生成的字节码指令。

         code_length为u4类型的长度值,理论上最大值可以达到2的32次方-1,虚拟机规范中规定方法不允许超过65535条字节码指令,如果超过了该限制,javac编译器会拒绝编译。

         code属性为class文件中的一个重要的属性,如果把一个java程序中信息一分为二,java代码(code,方法体里的java代码)和元数据(Metadata,包括类、字段、方法及其他信息)两部分。code属性则用于描述代码,所有其他数据项用于描述元数据。

(5)exception_info:异常表信息

         异常表中有四个字段:start_pc行到end_pc行(不包含end_pc行),出现了catch_type或其子类型异常,则跳转到handler_pc行继续处理。

         如果,catch_type的值为0,代表任何异常情况都要转向到handler_pc处进行处理。

 2、Exceptions属性

       该Exceptions属性是在方法表中与Code属性平级的一项属性。Exceptions属性列出的是可抛出的受检查的异常(编译时期的异常),也就是方法上声明的throws关键字后面列出的异常。

3、LineNumberTable属性

      该属性用于描述java源码行号与字节码行号之间的对应关系。该属性并不是运行时的必须属性。

4、LocalVariableTable属性

      用于描述栈帧中局部变量表中的变量与java源码中定义变量的关系,运行时非必须的属性。

5、SourceFile属性

      描述生成class文件的源码文件的名称。该属性为可选属性。

6、ConstantValue属性

      该属性通知虚拟机为静态变量自动赋值。

      对于实例变量赋值是在实例构造器<init>方法中进行的;

      类变量两种方式赋值:

              ①类构造器<client>方法中进行。

              ②使用constantvalue属性来赋值。

     如果final,static同时,修饰一个变量则为常量,并且该常量类型的为基本数据类型或者是字符串类型,就生成ConstantValue属性来进行初始化;如果变量未被final修饰,并且为非基本类型以及字符串类型,则选择在<clinit>方法中初始化。

7、innerclass属性

      描述内部类与外部类之间的关联。

上述内容参考自  周志明 《深入理解虚拟机》一书。。。

猜你喜欢

转载自1498116590.iteye.com/blog/2409816