JVM 学习笔记目录:
JVM探索之路之Class文件结构解析(一) :Class文件的格式与定义
JVM探索之路之Class文件结构解析(三):访问修饰符、类索引、父类索引与接口索引集合
常量池
上一篇博文介绍了Class文件的“魔数”和“主次版本号”,常量池数据项目的入口是紧接着“主次版本号”数据项目的。Class文件的常量池是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时也是Class文件中第一个表类型的数据项目。为了方面讲解和查看下面给出Class文件结构表和实例Class文件:
package com.beliefbetrayal.clazz;
public class ClassFileTest {
private int m;
public int getM() {
return m;
}
public void setM(int m) {
this.m = m;
}
}
无论是无符号数还是表类型,当需要描述同一类型但数量不定的多个数据时。经常会使用一个前置的容量计数器加上若干个连续的数据项形式。因为Class的常量池中的常量是不固定的,所以常量池的入口需要前置一个容量计数器“contant_pool_count”用于记录常量池中常量的数目,它是u2(2个字节)类型的。 用WinHex打开该Class文件获得下图:
图为Class文件的部分信息1
该常量池的容量计数器的索引是从1开始的而不是从0开始,将0索引空出来时为了满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量”的意思。因为量池的容量计数器为u2,所以它的值为"0x0018",换算成10进制数为24,代表了常量池中有23个常量索引为1~23。为了验证准确性,我们可以使用JDK为我们提供的javap工具,下图为使用javap分析Class文件的部分截图(只列出了常量池部分):
可也从图中的信息看出,该Class文件确实有23个常量。下面来具体分析常量池,常量池之中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量一般为“文本字符串”、“被声明为final的常量值”等等。符号引用一般为“类和接口的全限定名(Fully Qualified Name)”、“字段的名称和描述符(Descriptor)”和“方法的名称和描述符”。
常量池中的每一项常量都是一个表类型,在JDK6.0之前有11种结构的表数据,这11种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位(tag,取值为1~12,标志2空缺),它代表了当前这个常量属于那种常量类型,11种常量类型所代表的具体含义如下图所示:
回头查看“Class文件的部分信息1”图,第一个常量,它的标志位为"0x07",转换为10进制数为7,查看“常量池项目类型”表可以发现该常量属于CONSTANT_Class_info类型,次类型的常量代表一个类或接口的符号引用。CONSTANT_Class_info的结构图如下:
tag标志位已经解释过了,name_index是一个索引值,它指向常量池中一个CONSTANT_Utf8_info类型的常量,此常量代表了这个类(接口)的全限定名,查看“Class文件的部分信息1”图,name_index的值为"0x0002",转换为10进制数为2,指向常量池中第二个常量。继续查看“Class文件的部分信息1”图,查找第二个常量的标志为"0x01",转换为10进制数为1,查看“常量池项目类型”表可知确实是一个CONSTANT_Utf8_info类型的常量。 CONSTANT_Utf8_info类型的常量结构:
length表示这个UTF-8编码的字符串长度是多少字节,它后面紧跟着长度为length的UTF-8编码方式的字符串。查看“Class文件的部分信息1”图,length的值为"0x0026",转换成10进制数为38,表示后面紧跟着长度为38个字节长度的字符串,参看“ Class文件的部分信息2”图:
图为Class文件的部分信息2
查看图片的右边,可以看到被选中的38个字节长度的值为:com/beliefbetrayal/clazz/ClassFileTest这正是我们示例类的全限定名。分析了这么多,我们现在查看用javap工具分析出来的常量池信息(查看常量池信息),可以看到“constant #1 = class #2”这条信息指明了该常量指向#2常量,“constant #2 = Asciz com/beliefbetrayal/clazz/ClassFileTest”这条信息指明了该常量的值为“com/beliefbetrayal/clazz/ClassFileTest”可以看到我们的分析都是正确的。
根据“Class文件的部分信息2”图第三个常量的标志位为"0x07"转换为10进制为7,查看“常量池项目类型”表可以发现该常量属于CONSTANT_Class_info类型,次类型的常量代表一个类或接口的符号引用。根据“CONSTANT_Class_info的结构图”,name_index值为"0x0004",转换为10进制数为4,表示指向第四个常量。 根据“Class文件的部分信息2”图第四个常量的标志为"0x01"转换为10进制数为1,查看“常量池项目类型”表可知是一个CONSTANT_Utf8_info类型的常量,根据CONSTANT_Utf8_info类型结构图,length的值为"0x0010"转换为10进制数为16,表示后面紧跟着16个字节长度的字符串,如下图:
图为Class文件的部分信息3
查看图片的右边,可以看到被选中的16个字节长度的值为:java/lang/Object。 我们现在再回去查看用javap工具分析出来的常量池信息(查看常量池信息),可以很开心的看到我们的分析又被验证了^_^。再接再厉,分析第5个常量,它的标志为"0x01",转换为10进制数为1 ,查看“常量池项目类型”表可知是一个CONSTANT_Utf8_info类型的常量,根据CONSTANT_Utf8_info类型结构图,length的值为"0x0001"转换为10进制数为1,表示后面紧跟着1个字节长度的字符串,如下图:
图为Class文件的部分信息4
查看图片的右边,可以看到被选中的1个字节长度的值为:“m”它是我们定义的成员变量的名称。到此为止我们分析了ClassFileTest常量池中23个常量中的5个,其余的常量都可以使用类似的方法计算出来。手工推导出来后与javap工具分析出来的信息对比可以验证推导的正确性。下面将常量池中的11种常量类型的结构表列出来:
查看用javap工具分析出来的常量池信息(查看常量池信息),仔细观察会发现有许多常量出现的莫名其妙,如:“I”, “LineNumberTable”等,这些自动生成的常量会被后面字段表,方法表和属性表引用,他们会被用来描述一些不方便使用“固定字节”来表达的内容。
常量池的介绍结束了,我们不需要将每一个常量都手工计算出来,主要是要掌握好方法,JDK已经为我们提供了功能强大的Class文件分析工具javap。