3 常量池
紧接着主次版本号之后的是常量池入口,常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。
由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池中常量计数值(constant_pool_count)。与Java中语言习惯不一样的是,这个容量计数是从 1 开始而不是从 0 开始的,如图所示,常量池容量(偏移地址为0x00000008)为十六进制0x0013,即十进制的 19,这就代表常量池中有 18 项常量,索引值范围为1 ~ 18。在Class文件格式规范制定之时,设计者将第 0 项常量空出来是由特殊考虑的,这样做的目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况就可以把索引值置为 0 来表示。Class文件结构中只有常量池的容量计数是从 1 开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从 0 开始的。
常量池中主要存放两大类常量:字面值(Literal)和符号引用(Symbolic References)。字面值比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面几类常量:
- 被模块导出的包或者开放的包(Package)
- 类和接口的全限定名(Fully Qualified Name)
- 字段名称和描述符(Descripor)
- 方法的名称和描述符
- 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
- 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
Java代码在进行javac编译的时候,并不像C和C++那样有"连接"这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。
当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。
常量池中的每一项常量都是一个表,最初常量表中共有 11 种结构各不相同的表结构数据,后来为了更好地支持动态语言调用,额外增加了 4 中动态语言相关的常量,为了支持Java模块化系统(Jigsaw),又加入了CONSTANT_Module_info和CONSTANT_Package_info两个常量,所以截止JDK13,常量表中分别有17种不同类型的常量。
这17种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位(tag,取值见下图),代表当前这个常量属于那种常量类型。

之所以说常量池是最繁琐的数据,是因为这17种常量类型各自有完全独立的数据结构,两两之间没有什么共性和联系,因此只能逐项进行讲解。

常量池的第一项常量,它的标志位(偏移地址:0x0000000A)是0x0A,查表得知,这个常量属于 ...