前言

这里是我在学习常量池相关知识时遇到的一些问题。


在Java编译器编译Java文件时,会把字面量符号引用加入到class文件的常量池中。

java interface 常量 String_常量池


我们是针对int类型的值和long、double、float等类型的值进行比较。

上面我们可以看到基本数据类型的值会加入到常量池中,那我们就来看一下是否如此:

public class ConstPoolCase {
    int a = 1;
    float b = 1f;
    double c = 2d;
    long d = 3L;
    public static void main(String[] args) {
        //todo
    }
}

上面是一个很简单的例子,接下来我们使用javap -verbose来查看一下class文件。

java interface 常量 String_推送_02


我们可以清楚地看到long、double、float型的数值确实是加入到了常量池当中,可是int型呢,int型的数值怎么没有出现在常量池当中。

为了一探究竟,所以我决定通过相关的字节码指令,查看这些普通基本类型是如何被赋值的。如图:

java interface 常量 String_常量池_03


我们可以看到int型使用的指令和其他三种类型不太相同,其他三种类型基本上是类似的指令操作。

我们来看一下这些指令的作用:

iconst_1作用是将int型(1)推送至栈顶,该指令属于const系列,该系列的命令主要是负责将简单的数值推送到栈顶,该系列的命令是不需要带参数的。

首先通过通过iconst_1指令将int型的1推送至栈顶,再通过putfiled指令,将1赋值给了字段a,由此可以知道,如果int类型的数值如果比较简单的话,是不会将该值放入常量池中的,可以直接通过JVM指令将值赋值给相应字段。

ldc: 将int, float或String型常量值从常量池中推送至栈顶。
ldc_w: 将int, float或String型常量值从常量池中推送至栈顶(宽索引)
ldc2_w: 将long或double型常量值从常量池中推送至栈顶(宽索引)
这三个指令是属于ldc系列的指令,该系列的指令负责将数值常量或String常量值从常量池中推送到栈顶。该命令后面需要给一个表示常量在常量池中位置(编号)的参数。

通过了解到ldc系列的指令,并且我们可以看到,ldc指令后面的参数#3,#5,#8是和第一幅图相对应的。
有没有注意到,ldc系列是可以将int型常量值从常量池中推送至栈顶的,那就是说,int型的数值其实是会加入到常量池中的。
带着这个疑问,我们继续来看一下push系列的指令。

bipush 将单字节的常量值(-128~127)推送至栈顶
sipush 将一个短整型常量值(-32768~32767)推送至栈顶
该系列命令负责把一个整形数字(长度比较小)送到到栈顶。该系列命令有一个参数,用于指定要送到栈顶的数字。
注意该系列命令只能操作一定范围内的整形数值,超出该范围的使用将使用ldc命令系列。

看到这里我们就恍然大悟了,原来JVM字节码的指令是可以存储一定范围的值的,不需要将该范围内的值放置到常量池当中,然后搞个索引指向常量池中。
我们来验证一下:

public class ConstPoolCase {

    int a = 32768;
    float b = 11f;
    double c = 12d;
    long d = 3L;

    public static void main(String[] args) {
        //todo
    }
}

我们将int的值设置为32768,大于short型的最大值,可以看到,int型的数值出现在了常量池当中。

java interface 常量 String_JVM系列_04


相应的字节码指令也变成了ldc.

java interface 常量 String_字节码_05

由此可以得出结论:

对于const系列命令和push系列命令操作范围之外的数值类型常量,都放在常量池中的。