--- 常量池及相关内容 --- #### 目录 1. 常量项的类型和关系 2. 信息描述规则 3. field_info 和 method_info 4. access_flags 介绍 #### 常量项的类型和关系 在 JVM 规范中,常量池的英文叫 Constant Pool,对应的数据结构就是一个类型为 cp_info 的数组,每一个 cp_info 对象存储了一个常量项。cp_info 对应数据结构的伪代码如下: ```java cp_info{ u1 tag; //常量项的类型 u1 info[]; //常量项的内容 } ``` 由伪代码可知,每一个常量项的第一个字节用于表示常量项的类型,紧接其后的才是具体的常量项内容。那么,常量项会有哪些类型呢? | 常量项类型 | tag 取值 | 含义 | | --------------------------- | -------- | ------------------------------------------------------------ | | CONSTANT_Class | 7 | 代表类或接口的信息 | | CONSTANT_Fieldref | 9 | 同下 | | CONSTANT_Methodref | 10 | 这三种常量项有相似的内容,分别存储成员变量、成员函数和接口函数信息。这些信息包括所属类的类名、变量和函数名、函数参数、返回值类型等 | | CONSTANT_InterfaceMethodref | 11 | 同上 | | CONSTANT_String | 8 | 代表一个字符串(String)。注意,该常量项本身不存储字符串的内容,它只存储了一个索引值 | | CONSTANT_Integer | 3 | Java 中,int 和 float 型数据的长度都是 4 个字节。这两种常量项分别代表 int 和 float 型数据的信息 | | CONSTANT_Float | 4 | 同上 | | CONSTANT_Long | 5 | Java 中,long 和 double 型数据的长度都是 8 个字节。这两种常量项分别代表 long 和 double 型数据的信息 | | CONSTANT_Double | 6 | 同上 | | CONSTANT_NameAndType | 12 | 这种类型的常量项用于描述类的成员域或成员函数相关的信息 | | CONSTANT_Utf8 | 1 | 用于存储字符串的常量项。注意,该项真正包含了字符串的内容。而 CONSTANT_String 常量项只存储了一个指向 CONSTANT_Utf8 项的索引 | | CONSTANT_MethodHandle | 15 | 用于描述 MethodHandle 信息。MethodHandle 和反射有关系。Java 类库中对应的类为 java.lang.invoke.MethodHandle | | CONSTANT_MethodType | 16 | 用于描述一个成员函数的信息,只包括函数的参数类型和返回值类型,不包括函数名和所属类的类名 | | CONSTANT_InvokeDynamic | 18 | 用于 invokeDynamic 指令。invokeDynamic 和 Java 平台上实现了一些动态语言(如 Python、Ruby)相类似的有关功能 | ##### CONSTANT_String 和 CONSTANT_Utf8 的区别 CONSTANT_Utf8: 该常量项真正存储了字符串的内容。下面我们将看到此类型常量项对应的数据结构中有一个字节数组,字符串就存储在这个字节数组中。 CONSTANT_String: 代表一个字符串,但是它本身不包含字符串的内容,而仅仅包含一个指向类型为 CONSTANT_Utf8 常量项的索引。 下面我们看看几种常见常量项的内容,如下: ```xml CONSTANT_Utf8_info{ u1 tag; u2 length; u1 bytes[length]; } CONSTANT_Class_info{ u1 tag; u2 name_index; } CONSTANT_Fieldref_info{ u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_String_info{ u1 tag; u2 string_index; } CONSTANT_MethodType_info{ u1 tag; u2 descriptor_index; } CONSTANT_Methodref_info{ u1 tag; u2 class_index; u2 name_and_type_index; } CONSTANT_NameAndType_info{ u1 tag; u2 name_index; u2 descriptor_index; } CONSTANT_InterfaceMethodref_info{ u1 tag; u2 class_index; u2 name_and_type_index; } ``` 首先看 CONSTANT_Utf8_info,其中 length 表示 bytes 数组的长度,而 bytes 成员则真正存储字符串的内容。了解这一点信息很重要,因为凡是需要表示字符串的地方实际上都是指向常量池中一个类型为 CONSTANT_Utf8_info 元素的索引。 比如 Class_info 中的 name_index、String_info 中的 string_index、MethodType_info 中的 descriptor_index 等等,都代表一个指向类型为 CONSTANT_Utf8_info 元素的索引。 我们在看下基本数据类型常量项对应的数据结构: ``` CONSTANT_Long_info{ u1 tag; u4 high_bytes; u4 low_bytes; } CONSTANT_Integer_info{ u1 tag; u4 bytes; } CONSTANT_Double_info{ u1 tag; u4 high_bytes; u4 low_bytes; } CONSTANT_Float_info{ u1 tag; u4 bytes; } ``` 我们可以看到以上结构体内直接就能存储数据,这几个 info 之间没有引用关系。 对于前面的 info 而言,不在每个常量项里直接包含字符串信息,而是采用间接引用元素索引的方式,这是**为了节省 Class 文件的空间**,很好理解就不用多说了。 **除了采用引用索引的方式以节省空间外,规范对用于描述成员变量、成员函数相关的字符串的格式也有要求。** #### 信息描述规则 根据 Java 虚拟机规范,如何用字符串来描述成员变量、成员函数是有讲究的,这些规则主要集中在数据类型、成员变量和成员函数的描述规则。包括 - 数据类型的描述规则 - 成员变量的描述规则,即 Field Descriptor - 成员函数的描述规则,即 Method Descriptor ##### 数据类型的描述规则 | 类型 | 描述 | | ------- | ------------------ | | byte | B | | char | C | | double | D | | float | F | | int | I | | long | J | | short | S | | boolean | B | | String | Ljava/lang/String; | | int[] | [I | ##### 成员变量描述规则 就是类似上面的数据类型。 ##### 成员函数描述规则 和成员变量略有不同的是,一个成员函数(Method Description)的描述需要包含返回值及参数的数据类型。 比如,对于 System.out.print(String str) 函数,它的 Method Description 是: ```java (Ljava/lang/String;)V ``` Method Description 不包含函数名,这样做的目的其实也是为了节省空间,因为很多函数可能名字不同,但是它们的 Method Description 是一样的。 #### field_info 和 method_info 它们的数据结构伪代码如下: ```java field_info{ u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } method_info{ u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } ``` - access_flags 访问标志 - name_index 指向成员变量或成员函数的名字的 Utf8_info 常量项。 - descriptor_index 也指向 Utf8_info 常量项,其内容分别是描述成员变量的 FieldDescriptor 和描述成员函数的 MethodDescriptor。 - attributes 为属性信息,成员域和成员函数都包含若干属性。 既然 method_info 描述的是一个成员函数,那么这个函数对应的代码经过编译后得到的 Java 字节码存储在什么地方呢? 其实存储在属性中。 #### access_flags access_flags 其实就是访问修饰符,如下: | 标识位 | 说明 | | ---------------- | -------------------- | | ACC_PUBLIC | public | | ACC_FINAL | final | | ACC_INTERFACE | interface | | ACC_ABSTRACT | abstract | | ACC_ANNOTATION | annotation | | ACC_ENUM | 枚举类型 | | ACC_PROTECT | protect | | ACC_STATIC | static | | ACC_VOLATILE | volatile | | ACC_TRANSIENT | transient | | ACC_NATIVE | native | | ACC_VARARGS | 可变长参数个数的函数 | | ACC_SYNCHRONIZED | synchronized | | ... | ... | 之前写过,关于 synchronized 方法的实现原理,是在方法前面加上 ACC_SYNCHRONIZED 的标识位,原本我以为只有 synchronized 方法会有这种标识位,其他一些访问修饰符都没有,看来还是 too young too simple~