class常量池

85 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情

class常量池是Class文件中非常重要的一个数据区域,里面存储着很多结构体,这些结构体保证了我们java程序可以正常运行。下文理解int、float、long、double常量池项以及对应结构体。

class常量池

class常量池是很重要的一个数据区。

class常量池在什么位置

class常量池在class文件中的什么位置?

如下图,在主版本号之后的区域就是常量池相关的数据区了。首先是两个字节的常量池计数器,紧接着就是常量池数据区。

image-20220807010456561.png

常量池计数器的数值为何比常量池项数量大一?

常量池计数器是从1开始计数的而不是0,如果常量池计数器的数值为15那么常量池中常量项(cp_info)的数量就为14。常量池项个数 = constant_count-1。

将第一位空出来是有特殊考虑的,当某些索引表示不指向常量池中任何一个常量池项的时候,可以将索引设置为0。

有哪些cp_info

常量池项(cp_info)记录着class文件中的字面量信息。那么存在多少中cp_info,以及如何区分。

cp_info中存在着一个tag属性,jvm会根据tag值来区分不同的常量池项

Tag结构说明
1CONSTANT_Utf8_info字符串常量值
3CONSTANT_Integer_infoINT类型常量
4CONSTANT_Float_intoFLOAT类型常量
5CONSTANT_Double_infoDOUBLE类型常量
7CONSTANT_Class_info类或接口全限定名常量
8CONSTANT_String_infoString类型常量对象
9CONSTANT_Fieldref_info类中的字段
10CONSTANT_Methodref_info类中的方法
11CONSTANT_InterfaceMethodref_info所实现接口的方法
12CONSTANT_NameAndType_info字段或方法的名称和类型
15CONSTANT_MethodHandler_info方法句柄
16CONSTANT_MethodType_info方法类型
18CONSTANT_InvokeDynamic_info表示动态的对方法进行调用
int和float的cp_info

int的常量池项结构为CONSTANT_Integer_info。float的常量池项结构为CONSTANT_Float_info。且这两种数据类型所占空间都为四个字节。所对应的结构如下:

image-20220807134442528.png

例子1

编译过后使用javap -v分析

public class CpInfoIntAndFloat {
    private final int i1 = 1;
    private final int i2 = 1;

    float f1 = 20f;
    Float f2 = 20f;
    Float f3 = 20f;
    float f4 = 30f;
}

确实在constant_pool中存在着我们预期的cp_info结构。且不存在重复结构。

image-20220807144906700.png

但是这里我们特意将int的修饰符设置为final类型的。如果不是final类型的对于int i1 = 1来说并不会在constant_pool中存入CONSTANT_Integer_info结构体。我们可以试一下

例子2

public class CpInfoIntAndFloat2 {
    private  int i1 = 0;
    private  int i2 = 5;
    private  int i3 = -127;
    private  int i4 = 128;

    private  int i5 = 32767;
    private  int i6 = -32768;

    static int i11 = 1;
}

使用javap -v CpInfoIntAndFloat2> 1.txt命令将分解信息输出到1.txt文件方便查看:

发现并没有Integer相关的cp_info。且我们声明了一个 static int i11 = 1;静态的成员变量(类变量),编译器会为我们生成一个cinit方法。我们去查看一下initcinit方法。

image-20220807151725367.png

查看一下init方法。发现在实例初始化的时候会调用init方法,会使用iconst_X命令、bipush命令以及sipush为我们的int类型变量赋值。对于较小的int类型变量(小于5)会使用iconst_X命令,不需要参数,直接赋值。对于较大的(-128,127)使用bipush,带上数值大小参数,直接赋值,对于再大一点的数值使用sipush命令赋值。

image-20220807151815676.png

例子3

那么对于比32767大也就是比short范围大的int类型呢?

结论是会存入constant_pool常量池的。

public class CpInfoIntAndFloat3 {
    private  int i1 = 32768;
    private  int i2 = 32769;
    private  int i3 = 42768;
}

image-20220807152348377.png

查看一下init方法看对于存入constant_pool常量池的项,是如何赋值的

会使用ldc命令从常量池中取,然后再赋值。

image-20220807152445635.png

结论

那我么就可以得出结论了:

  • iconst_x命令,会对 0 - 5范围内的值进行直接赋值,且无需参数
  • bipush(byteintpush)命令,会对 -128 127 范围内的值进行直接赋值,需要携带字面量参数
  • sipush(shortintpush)命令,会对 -32768 32767范围内的值进行直接赋值,需要携带字面量参数
  • 超过如上范围的值,会存入constan_pool常量池,使用LDC命令取值,再赋给对应字段
long&double

Long的常量池项结构为CONSTANT_Long_info。double的常量池项结构为CONSTANT_Double_info。且这两种数据类型所占空间都为8个字节。所对应的结构如下:

image-20220807153756946.png

会将对应结构存入constant_pool中,且所有使用到对应结构的字段都会指向它

image-20220807154027858.png

image-20220807154119650.png