常量池。

        紧接着主次版本号(0016)之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,他是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时他还是在Class文件中第一个出现的表类型数据项目。

        由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。与Java中语言习惯不一样的是,这个容量计数是从1而不是0开始的,如下图所示,常量池容量(偏移地址:0x00000008)为十六进制数0x0016,即十进制的22,这就代表常量池中有21项常量,索引值范围为1~21。在Class文件格式规范制定之时,设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于满足后面某些指向常量吃的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况就可以把索引值置为0来表示。Class文件结构只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。


        常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:

  • 类和接口的全限定名(Fully Qualified Name)
  • 字段的名称和描述符(Descriptor)
  • 方法的名称和描述符

        Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

        常量池中每一项常量都是一个表,在JDK 1.7之前共有11种结构各不相同的表结构数据,在JDK 1.7中为了更好的支持动态语言调用,又额外增加了3种(CONSTANT_MethodHandle_inof、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info)。

        这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位(tag,取值间下表中标志列),代表当前这个常量属于哪种常量类型。这14种常量类型所代表的具体含义见下表。

类型标志描述
CONSTANT_Utf8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整型字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的部分符号引用
CONSTANT_MethodHandle_info15表示方法句柄
CONSTANT_MethodType_info16标识方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

         之所以说常量池是最繁琐的数据,是因为这14种常量类型各自均有自己的结构。回头看看第一个图中常量池的第一项常量,他的标志位(偏移地址:0x0000000A)是0x07,查上表的标志列发现这个常量属于CONSTANT_Class_info类型,此类型的常量代表一个类或者接口的符号引用。CONSTANT_Class_info的结构比较简单,见下表。

类型名称数量
u1tag1
u2name_index1

        tag是标志位,上面已经讲过了,他用于区分常量类型:name_index是一个索引值,他指向常量池中一个CONSTANT_Utf8_info类型常量,此常量代表了这个类(或者接口)的全限定名,这里name_index值(偏移地址:0x0000000B)为0x0002,也即是指向了常量池中的第二项常量。继续从第一个图中查找第二项常量,他的标志位(地址:0x0000000D)是0x01,查第一个表可知确实是一个CONSTANT_Utf8_info类型的常量。CONSTANT_Utf8_info类型的结构,见下表。

类型名称数量
u1tag1
u2length1
u1byteslength

        length值说明了这个UTF-8编码的字符串长度是多少字节,他后面紧跟着的长度为length字节的连续数据是一个使用UTF-8缩略编码表示的字符串。UTF-8缩略编码与普通UTF-8编码的区别是:从'\u0001'到'\u007f'之间的字符(相当于1~127的ASCII码)的缩略编码使用一个字节表示,从'\u0080'到'\u07ff'之间的所有字符的缩略编码用两个字节表示,从'\u0800'到'\uffff'之间的所有字符的缩略编码就按照普通UTF-8编码规则使用三个字节表示。

        由于Class文件中方法、字段等都需要引用CONSTANT_Utf8_info型常量来描述名称,所以CONSTANT_Utf8_info型常量的最大长度也就是Java中方法、字段名的最大长度。而这里的最大长度就是length的最大值,即u2类型能表达的最大值65535。所以Java程序中如果定义了超过64KB英文字符的变量或方法名,将会无法编译。

        本例中这个字符串的length值(偏移地址:0x0000000E)为0x001D,也就是长29字节,往后29字节正好都在1~127的ASCII码范围以内,内容为“org/fenixsoft/clazz/TestClass”,换算结果如下图选中的部分所示。


        到此为止,我们分析了TestClass.class常量池中21个常量中的两个,其余的19个常量都可以通过类似的方法计算出来。在JDK的bin目录中,Oracle公司已经为我们准备好一个专门用于分析Class文件字节码的工具:javap,下面代码列出了使用javap工具的-verbose参数输出的TestClass.class文件字节码内容(次清单中省略了常量池以外的信息)。


       从上面代码中可以看出,计算机已经帮我们把整个常量池都计算了出来,并且第1、2项常量的计算结果与我们手工计算的结果一致。仔细看一下会发现,其中一些常量似乎从来没有在代码中出现过,如“I”、“V”、“<int>”、“LineNumberTable”、“LocalVariableTable”等,这些看起来在代码任何一处都没有出现过的常量是哪里来的呢?

        这部分自动生成的常量的确没有在Java代码里面直接出现过,但他们会被字段表(field_info)、方法表(method_info)、属性表(attribute_info)引用到,他们会用来描述一些不方便使用“固定字节”进行表述的内容。譬如描述方法的返回值是什么?有几个参数?每个参数的类型是什么?因为Java中的“类”是无穷无尽的,无法通过简单地无符号字节来描述一个方法用到了什么类,因此在描述方法的这些信息时,需要引用常量表中的符号引用进行表达。最后,将14种常量项的结构定义总结为下表以供参考。

常量项目类型描述
CONSTANT_Utf8_infotag
length
bytes
u1
u2
u1
值为1
UTF-8编码的字符串占用的字节数
长度为length的UTF-8编码的字符串
CONSTANT_Integer_infotag
bytes
u1
u4
值为3
按照高位在前存储的int值
CONSTANT_Float_infotag
bytes
u1
u4
值为4
按照高位在前存储的float值
CONSTANT_Long_infotag
bytes
u1
u8
值为5
按照高位在前存储的long值
CONSTANT_Double_infotag
bytes
u1
u8
值为6
按照高位在前存储的double值
CONSTANT_Class_infotag
index
u1
u2
值为7
指向全限定名常量项的索引
CONSTANT_String_infotag
index
u1
u2
值为8
指向字符串字面量的索引
CONSTANT_Fieldref_infotag
index
index
u1
u2
u2
值为9
指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
指向名称及类型描述符CONSTANT_NameAndType的索引项
CONSTANT_Methodref_infotag
index
index
u1
u2
u2
值为10
指向声明方法的类描述符CONSTANT_CLass_info的索引项
指向名称及类型描述符CONSTANT_NameAndType的索引项
CONSTANT_InterfaceMethodref_infotag
index
index
u1
u2
u2
值为11
指向声明方法的接口描述符CONSTANT_Class_info的索引项
指向名称及类型描述符CONSTANT_NameAndType的索引项
CONSTANT_NameAndType_infotag
index
index
u1
u2
u2
值为12
指向该字段或方法名称常量项的索引
指向该字段或方法描述符常量项的索引
CONSTANT_MethodHandle_infotag
reference_kind
reference_index
u1
u1
u2
值为15
值必须在·~9之间(包括1和9),他决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
值必须是对常量池有效索引
CONSTANT_MethodType_infotag
descriptor_index
u1
u2
值为16
值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示方法的描述符
CONSTANT_InvokeDynamic_infotag
bootstrap_method_attr_index
name_and_type_index
u1
u2
u2
值为18
值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引
值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值