运行时常量池

常量池

想要搞清楚「运行时常量池」,就必须先弄清楚「常量池」是什么?!

「常量池」就在 Class 文件中,通过javap -v xxx就可以看到 Class 文件反编译后的内容,其中有一项Constant pool就是常量池,它用于存放编译期生成的各种字面量与符号引用

字面量比较接近于 Java 语言层面的常量池概念,如:文本字符串、被声明为 final 的常量值等

常量池的项目类型中只有五类字面量:整型字面量、浮点型字面量、长整型字面量、双精度浮点型字面量、字符串类型字面量

关于符号引用和直接引用的详细内容可见 类加载的过程 的解析部分!!

好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如:字符串常量池,在编译阶段就把所有的字符串放到一个常量池中

一个例子

主要看一下常量池中有哪些我们定义的字面量,可以找到有:200000, 1, 3, abc

根据上面说的「文本字符串、被声明为 final 的常量值」肯定在常量池中,所以1, 3, abc毫无疑问,那为什么有200000但没有2呢???

这就要先回到为静态变量b, c赋值的地方!!我们知道静态变量是在类加载的初始化阶段赋实际值,详情可见 类加载的过程-初始化

对应的反编译代码如下:

可以看到,为静态变量b赋值的字节码指令为:iconst_2;而为静态变量c赋值的字节码指令为:ldc #6,其中#6是字面量200000的符号引用

这显然和字节码指令有关,与之有关的字节码有:iconst, bipush, sipush, ldc。当 int 类型

iconst 指令

当 int 取值 -1 ~ 5 时,Java 虚拟机使用iconst指令将常量压入栈中

总结:int 取值 0 ~ 5 时 Java 虚拟机采用iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5指令将常量压入栈中,取值 -1 时采用iconst_m1指令将常量压入栈中

bipush 指令

当 int 取值 -128 ~ 127 时,Java 虚拟机使用bipush xxx指令将常量压入栈中,其中xxx为常量值

sipush 指令

当 int 取值 -32768 ~ 32767 时,Java 虚拟机使用sipush xxx指令将常量压入栈中,其中xxx为常量值

ldc 指令

当 int 取值 -2147483648 ~ 2147483647 时,Java 虚拟机使用ldc #n指令将常量压入栈中,其中#n为符号引用

注意:这条指令和前三条指令都不同,该指令的参数是通过符号引用从常量池中获得

运行时常量池

当 Class 文件被加载到内存后,「常量池」被存放的地方就叫「运行时常量池」,它在方法区中!!

当 Class 文件加载到内存中的时候,有些符号引用被转化为直接引用 (如:静态常量,静态方法等);而有些符号引用只有在每一次运行期间才会转化成直接引用

所以除了保存 Class 文件中描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中!!

「常量池」也被称为「静态常量池」,和「运行时常量池」呼应上了!!所以运行时常量池另外一个重要的特性时具备动态性

Java 语言并不要求常量一定只能编译期才能产生,也就是说,并非预置入 Class 文件中常量池的内容才能进入方法区运行时常量区,运行期间也可以将新的常量放入池中

这种特性被开发人员利用得比较多的便是String类的intern()方法,详情可见 字符串常量池