携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第9天,点击查看活动详情
class常量池是Class文件中非常重要的一个数据区域,里面存储着很多结构体,这些结构体保证了我们java程序可以正常运行。下文理解int、float、long、double常量池项以及对应结构体。
class常量池
class常量池是很重要的一个数据区。
class常量池在什么位置
class常量池在
class
文件中的什么位置?如下图,在主版本号之后的区域就是常量池相关的数据区了。首先是两个字节的常量池计数器,紧接着就是常量池数据区。
常量池计数器的数值为何比常量池项数量大一?
常量池计数器是从1开始计数的而不是0,如果常量池计数器的数值为15那么常量池中常量项(cp_info)的数量就为14。常量池项个数 = constant_count-1。
将第一位空出来是有特殊考虑的,当某些索引表示不指向常量池中任何一个常量池项的时候,可以将索引设置为0。
有哪些cp_info
常量池项(cp_info)记录着class文件中的字面量信息。那么存在多少中cp_info,以及如何区分。
cp_info中存在着一个tag属性,jvm会根据tag值来区分不同的常量池项
Tag | 结构 | 说明 |
---|---|---|
1 | CONSTANT_Utf8_info | 字符串常量值 |
3 | CONSTANT_Integer_info | INT类型常量 |
4 | CONSTANT_Float_into | FLOAT类型常量 |
5 | CONSTANT_Double_info | DOUBLE类型常量 |
7 | CONSTANT_Class_info | 类或接口全限定名常量 |
8 | CONSTANT_String_info | String类型常量对象 |
9 | CONSTANT_Fieldref_info | 类中的字段 |
10 | CONSTANT_Methodref_info | 类中的方法 |
11 | CONSTANT_InterfaceMethodref_info | 所实现接口的方法 |
12 | CONSTANT_NameAndType_info | 字段或方法的名称和类型 |
15 | CONSTANT_MethodHandler_info | 方法句柄 |
16 | CONSTANT_MethodType_info | 方法类型 |
18 | CONSTANT_InvokeDynamic_info | 表示动态的对方法进行调用 |
int和float的cp_info
int的常量池项结构为
CONSTANT_Integer_info
。float的常量池项结构为CONSTANT_Float_info
。且这两种数据类型所占空间都为四个字节。所对应的结构如下:
例子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结构。且不存在重复结构。
但是这里我们特意将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
方法。我们去查看一下init
和cinit
方法。
查看一下
init
方法。发现在实例初始化的时候会调用init
方法,会使用iconst_X
命令、bipush
命令以及sipush
为我们的int类型变量赋值。对于较小的int类型变量(小于5)会使用iconst_X
命令,不需要参数,直接赋值。对于较大的(-128,127)使用bipush,带上数值大小参数,直接赋值,对于再大一点的数值使用sipush
命令赋值。
例子3
那么对于比32767大也就是比short范围大的int类型呢?
结论是会存入constant_pool常量池的。
public class CpInfoIntAndFloat3 {
private int i1 = 32768;
private int i2 = 32769;
private int i3 = 42768;
}
查看一下init方法看对于存入constant_pool常量池的项,是如何赋值的
会使用ldc命令从常量池中取,然后再赋值。
结论
那我么就可以得出结论了:
- 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个字节。所对应的结构如下:
会将对应结构存入constant_pool中,且所有使用到对应结构的字段都会指向它