关于Java虚拟机二三事(六)---类文件结构(中)

1.前言

    在上节内容中,介绍了class文件中的魔数、主次版本号、常量池、访问标志以及类索引,父类索引和接口索引集合。本节将进一步介绍class文件中剩余部分:字段表集合、方法表集合以及属性表集合。


2.字段表集合

    字段表(field_info)用于描述接口或者类中声明的变量。字段(field_info)包括类级变量以及实例级变量,但不包括方法内部声明的局部变量。例如Java中描述的一个字段可以包含:

    1.字段的作用域(public、private、protect修饰符);

    2.是否为静态变量(static),可变性(final);

    3.并发可见性(volatile修饰符,是否强制从主内存读写);

    4.是否可被序列化(transient修饰符)、字段数据类型(基本数据类型、对象、数组);

    5.字段名称。

    上述信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池项来描述。

2.1 字段表结构

    所以,字段表集合的整体结构与常量池结构极为相似,即它是一个具有类似数组一样的结构,由于一个类中会有若干字段,因此,JVM在编译类的时候,会将类中定义好的字段数量设置到字段计数器中,然后将每一个字段的信息组成各自对应的每一个field_info的结构,依次存放在字段计数器之后。具体结构如下所示。

                            关于Java虚拟机二三事(六)---类文件结构(中)

    值得注意的是,这里提及的字段并非类中的方法内部定义的变量,而是针对类中定义的静态的或非静态的变量。

    比如,某一个类中定义了3个字段,那么JVM在编译该类的时候,会生成5个字段表(field_info),然后将字段表集合中的字段计数器的值设置为3,将三个字段表信息依次放置到字段计数器的后面

2.2 字段表集合在class文件中的位置

    字段表集合的位置紧跟在class文件中的接口索引集合之后,如下所示。

                                      关于Java虚拟机二三事(六)---类文件结构(中)

    

2.3 Java中的一个Field字段应该包含哪些信息?——字段表field_info结构体的定义

                关于Java虚拟机二三事(六)---类文件结构(中)

    针对上述表示,JVM虚拟机规范规定了field_info结构体来描述字段,其表示信息如下所示。

                关于Java虚拟机二三事(六)---类文件结构(中)

                关于Java虚拟机二三事(六)---类文件结构(中)

    下面会就field_info的组成元素进行一一讲解:访问标志(access_flag),名称索引(name_index)、描述索引(descriptor_index)、属性表集合

访问标志(access_flag)

    如上图所示定义的Field_info,field字段的访问标志(access_flag)占有2个字节,他能够表示如下信息。

                关于Java虚拟机二三事(六)---类文件结构(中)

    例如,我们在某个类中定义的field域:private static String str ;那么在访问标志上,第15ACC_PRIVATE和第13ACC_STATIC标志位都应该为1。

                关于Java虚拟机二三事(六)---类文件结构(中)

    如上图所示,str字段的访问标志位为0x000A,它由两个修饰符ACC_PRIVATE和组成,根据给定的访问标志(access_flag)。我们可以通过以下运算来得到这个域有哪些修饰符。

                关于Java虚拟机二三事(六)---类文件结构(中)

    上面列举的str字段的访问标志的值为000A,那么分别域上述的标志符的特征值取&,结果为1的只有ACC_PRIVATEACC_STATIC,所以该字段的标志符只有有ACC_PRIVATEACC_STATIC

                关于Java虚拟机二三事(六)---类文件结构(中)

字段的数据类型表示和字段名称表示

    class文件对数据类型的表示如下所示。

                关于Java虚拟机二三事(六)---类文件结构(中)

    field字段名称,我们定义了一个形如private static String str的field字段,其中“str”就是这个字段的名称。

    class文件将字段名称和field字段类型的数据类型表示作字符串存储在常量池中,在field_info结构体中,紧接着访问标志的,就是字段名称字段描述索引,他们分别占有2个字节,其内部存储的是指向了常量池的某个常量池项的索引对应的常量池项中存储的字符串,分别表示该字段的名称和字段描述符。

属性表集合------静态字段field字段的初始化

    在定义field字段的过程中,我们有时候会很自然地对field字段值直接赋值,如下所示。

[java] view plain copy
  1. public static final int MAX=100;  
  2. public  int count=0;  

    对于虚拟机而言,上述两个field字段赋值的时机是不同的:

    1.对于非静态的field字段的赋值将会出现在实例构造方法<init>()中

    2.对于静态的field字段,有两个选择,1、在静态构造方法<cinit>()中进行,2 使用ConstantValue属性进行赋值。

Sun javac编译器对于静态field字段的初始化赋值策略

目前的Sun javac编译器的选择是:如果使用finalstatic同时修饰一个field字段,并且这个字段是基本类型或者String类型的,那么编译器在编译这个字段的时候,会在对应的field_info结构体中增加一个ConstantValue类型的结构体,在赋值的时候使用这个ConstantValue进行赋值;如果该field字段并没有被final修饰,或者不是基本类型或者String类型,那么将在类构造方法<cinit>()中赋值。

    对于上述的public static final int MAX = 100;javac编译器在编译此field字段构建field_info结构体时,除了访问标志、名称索引、描述符索引,会增加一个ConstantValue类型的属性表。

                关于Java虚拟机二三事(六)---类文件结构(中)


2.4 实例解析:

    

    定义如下一个简单的Simple类,然后通过查看Simple.class文件内容并结合javap -v Simple 生成的常量池内容,分析str field字段的结构:

[java] view plain copy
  1. package com.louis.jvm;  
  2.   
  3. public class Simple {  
  4.   
  5.     private  transient static final String str ="This is a test";  
  6. }  

            关于Java虚拟机二三事(六)---类文件结构(中)

    

注:

1. 字段计数器中的值为0x0001,表示这个类就定义了一个field字段
2. 字段的访问标志0x009A,二进制是00000000 10011010,即第9、12、13、15位标志位为1,这个字段的标志符有:ACC_TRANSIENT、ACC_FINAL、ACC_STATIC、ACC_PRIVATE;

3. 名称索引中的值为0x0005,指向了常量池中的第5项,为“str”,表明这个field字段的名称是str

4. 描述索引中的值为0x0006,指向了常量池中的第6项,为"Ljava/lang/String;",表明这个field字段的数据类型是java.lang.String类型;

5.属性表计数器中的值为0x0001,表明field_info还有一个属性表;

6.属性表名称索引中的值为0x0007,指向常量池中的第7项,为“ConstantValue”,表明这个属性表的名称是ConstantValue,即属性表的类型是ConstantValue类型的;

7.属性长度中的值为0x0002,因为此属性表是ConstantValue类型,它的值固定为2

8.常量值索引 中的值为0x0008,指向了常量池中的第8项,为CONSTANT_String_info类型的项,表示“This is a test” 的常量。在对此field赋值时,会使用此常量对field赋值。

2.5 补充

    简单地说,对于一个类而言,它有两部分组成:field字段和 method方法。本文主要介绍了field字段,那还剩些一个method方法method方法啦。method方法可是说是class文件中最为重要的一部分了,它包含了方法的实现代码,即机器指令,机器指令是整个class文件的核心。