想要搞清楚「运行时常量池」,就必须先弄清楚「常量池」是什么?!
「常量池」就在 Class 文件中,通过javap -v xxx就可以看到 Class 文件反编译后的内容,其中有一项Constant pool就是常量池,它用于存放编译期生成的各种字面量与符号引用
字面量比较接近于 Java 语言层面的常量池概念,如:文本字符串、被声明为 final 的常量值等
常量池的项目类型中只有五类字面量:整型字面量、浮点型字面量、长整型字面量、双精度浮点型字面量、字符串类型字面量
关于符号引用和直接引用的详细内容可见 类加载的过程 的解析部分!!
常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。例如:字符串常量池,在编译阶段就把所有的字符串放到一个常量池中
==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等// 源码public class Test { public final int a = 1; // 常量 public static int b = 2; // 静态变量 public static int c = 200000; // 静态变量 public final static int d = 3; // 静态常量 public String s = "abc"; // 字符串}// 常量池Constant pool: #1 = Methodref #9.#30 // java/lang/Object."<init>":()V #2 = Fieldref #8.#31 // com/lfool/myself/Test.a:I #3 = String #32 // abc #4 = Fieldref #8.#33 // com/lfool/myself/Test.s:Ljava/lang/String; #5 = Fieldref #8.#34 // com/lfool/myself/Test.b:I #6 = Integer 200000 #7 = Fieldref #8.#35 // com/lfool/myself/Test.c:I #8 = Class #36 // com/lfool/myself/Test #9 = Class #37 // java/lang/Object #10 = Utf8 a #11 = Utf8 I #12 = Utf8 ConstantValue #13 = Integer 1 #14 = Utf8 b #15 = Utf8 c #16 = Utf8 d #17 = Integer 3 #18 = Utf8 s #19 = Utf8 Ljava/lang/String; #20 = Utf8 <init> #21 = Utf8 ()V #22 = Utf8 Code #23 = Utf8 LineNumberTable #24 = Utf8 LocalVariableTable #25 = Utf8 this #26 = Utf8 Lcom/lfool/myself/Test; #27 = Utf8 <clinit> #28 = Utf8 SourceFile #29 = Utf8 Test.java #30 = NameAndType #20:#21 // "<init>":()V #31 = NameAndType #10:#11 // a:I #32 = Utf8 abc #33 = NameAndType #18:#19 // s:Ljava/lang/String; #34 = NameAndType #14:#11 // b:I #35 = NameAndType #15:#11 // c:I #36 = Utf8 com/lfool/myself/Test #37 = Utf8 java/lang/Object主要看一下常量池中有哪些我们定义的字面量,可以找到有:200000, 1, 3, abc
根据上面说的「文本字符串、被声明为 final 的常量值」肯定在常量池中,所以1, 3, abc毫无疑问,那为什么有200000但没有2呢???
这就要先回到为静态变量b, c赋值的地方!!我们知道静态变量是在类加载的初始化阶段赋实际值,详情可见 类加载的过程-初始化
对应的反编译代码如下:
xxxxxxxxxxstatic {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: iconst_2 1: putstatic #5 // Field b:I 4: ldc #6 // int 200000 6: putstatic #7 // Field c:I 9: return LineNumberTable: line 14: 0 line 15: 4可以看到,为静态变量b赋值的字节码指令为:iconst_2;而为静态变量c赋值的字节码指令为:ldc #6,其中#6是字面量200000的符号引用
这显然和字节码指令有关,与之有关的字节码有:iconst, bipush, sipush, ldc。当 int 类型
iconst指令 bipush指令sipush指令ldc指令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指令将常量压入栈中
xxxxxxxxxx// 源码public class Test { public static int a = -1; public static int b = 5;}// 反编译static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: iconst_m1 1: putstatic #2 // Field a:I 4: iconst_5 5: putstatic #3 // Field b:I 8: return LineNumberTable: line 13: 0 line 14: 4bipush 指令
当 int 取值 -128 ~ 127 时,Java 虚拟机使用bipush xxx指令将常量压入栈中,其中xxx为常量值
xxxxxxxxxx// 源码public class Test { public static int a = -128; public static int b = 127;}// 反编译static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: bipush -128 2: putstatic #2 // Field a:I 5: bipush 127 7: putstatic #3 // Field b:I 10: return LineNumberTable: line 13: 0 line 14: 5sipush 指令
当 int 取值 -32768 ~ 32767 时,Java 虚拟机使用sipush xxx指令将常量压入栈中,其中xxx为常量值
xxxxxxxxxx// 源码public class Test { public static int a = -32768; public static int b = 32767;}// 反编译static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: sipush -32768 3: putstatic #2 // Field a:I 6: sipush 32767 9: putstatic #3 // Field b:I 12: return LineNumberTable: line 13: 0 line 14: 6ldc 指令
当 int 取值 -2147483648 ~ 2147483647 时,Java 虚拟机使用ldc #n指令将常量压入栈中,其中#n为符号引用
注意:这条指令和前三条指令都不同,该指令的参数是通过符号引用从常量池中获得
xxxxxxxxxx// 源码public class Test { public static int a = Integer.MIN_VALUE; public static int b = Integer.MAX_VALUE;}// 反编译static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #3 // int -2147483648 2: putstatic #4 // Field a:I 5: ldc #5 // int 2147483647 7: putstatic #6 // Field b:I 10: return LineNumberTable: line 13: 0 line 14: 5当 Class 文件被加载到内存后,「常量池」被存放的地方就叫「运行时常量池」,它在方法区中!!
当 Class 文件加载到内存中的时候,有些符号引用被转化为直接引用 (如:静态常量,静态方法等);而有些符号引用只有在每一次运行期间才会转化成直接引用
所以除了保存 Class 文件中描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中!!
「常量池」也被称为「静态常量池」,和「运行时常量池」呼应上了!!所以运行时常量池另外一个重要的特性时具备动态性
Java 语言并不要求常量一定只能编译期才能产生,也就是说,并非预置入 Class 文件中常量池的内容才能进入方法区运行时常量区,运行期间也可以将新的常量放入池中
这种特性被开发人员利用得比较多的便是String类的intern()方法,详情可见 字符串常量池