前言
最近在学习JVM运行时数据区中方法区的相关知识,其中涉及到常量池和运行时常量池,所以就把相关知识整理一下。
一、关于常量池
1.常量池是什么
常量池是字节码文件的一部分。JVM为每一个已经被加载的类型(类或者接口)都维护一个常量池,其中的数据项像数组项一样,通过索引访问。常量池中存储的数据类型:数量值、字符串值、类引用、方法引用、字段(属性)引用。
java字节码文件中,除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含常量池表(Constant Pool Table),在常量池表中包含了各种字面量和对类型、域(Field)、方法的符号的引用。
也就是说常量池中的数据以类似表的形式在常量池表中呈现,我们可以通过将编译后的字节码文件进行反编译来查看具体的内容,这里我们通过字节码查看工具jclasslib来查看。
2.查看常量池中的数据
代码源文件如下:
import java.util.Comparator;
public class ConstantPoolTest extends Object implements Comparator {
public static void main(String[] args) {
test1();
}
private static void test1() {
int count = 20;
System.out.println("count is:" + count);
}
@Override
public int compare(Object o, Object t1) {
return 0;
}
}
我们对以上的ConstantPoolTest 类进行编译,通过jclasslib查看,下图显示的是test1方法的字节码,其中含有"#"的地方表示对常量池中结构的调用,引用到了常量池中的内容,“#”后的数字表示索引,通过它进行访问。
可以看到,在istore_0执行完成后,int类型数据20倍存储到局部变量表中,随后,执行引擎执行到第3行时,需要用到常量池表中的第3项的内容, 点击“#3”可以看到该项具体的内容,显示此处需要用到System类,还需要调用#37和#38处的内容。
也可以通过 javap -v -p ConstantPoolTest 命令來查看:
Constant pool:
#1 = Methodref #12.#35 // java/lang/Object."<init>":()V
#2 = Methodref #11.#36 // jvm/ConstantPoolTest.test1:()V
#3 = Fieldref #37.#38 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Class #39 // java/lang/StringBuilder
#5 = Methodref #4.#35 // java/lang/StringBuilder."<init>":()V
#6 = String #40 // count is:
#7 = Methodref #4.#41 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #4.#42 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#9 = Methodref #4.#43 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#10 = Methodref #44.#45 // java/io/PrintStream.println:(Ljava/lang/String;)V
#11 = Class #46 // jvm/ConstantPoolTest
#12 = Class #47 // java/lang/Object
#13 = Class #48 // java/util/Comparator
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Ljvm/ConstantPoolTest;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 test1
#26 = Utf8 count
#27 = Utf8 I
#28 = Utf8 compare
#29 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;)I
#30 = Utf8 o
#31 = Utf8 Ljava/lang/Object;
#32 = Utf8 t1
#33 = Utf8 SourceFile
#34 = Utf8 ConstantPoolTest.java
#35 = NameAndType #14:#15 // "<init>":()V
#36 = NameAndType #25:#15 // test1:()V
#37 = Class #49 // java/lang/System
#38 = NameAndType #50:#51 // out:Ljava/io/PrintStream;
#39 = Utf8 java/lang/StringBuilder
#40 = Utf8 count is:
#41 = NameAndType #52:#53 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#42 = NameAndType #52:#54 // append:(I)Ljava/lang/StringBuilder;
#43 = NameAndType #55:#56 // toString:()Ljava/lang/String;
#44 = Class #57 // java/io/PrintStream
#45 = NameAndType #58:#59 // println:(Ljava/lang/String;)V
#46 = Utf8 jvm/ConstantPoolTest
#47 = Utf8 java/lang/Object
#48 = Utf8 java/util/Comparator
#49 = Utf8 java/lang/System
#50 = Utf8 out
#51 = Utf8 Ljava/io/PrintStream;
#52 = Utf8 append
#53 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#54 = Utf8 (I)Ljava/lang/StringBuilder;
#55 = Utf8 toString
#56 = Utf8 ()Ljava/lang/String;
#57 = Utf8 java/io/PrintStream
#58 = Utf8 println
#59 = Utf8 (Ljava/lang/String;)V
3.为什么需要常量池
java中的字节码需要数据支持,通常这种数据很大以至于不能直接存到字节码里,那么可以换另一种方式,将它们存到常量池里。通过在字节码里包含指向常量池的引用,来查找具体的地址。另外,由于字节码中不需要存储过大的数据(如上述类所继承的Object类、实现的Comparator接口),可以有效地减少字节码文件的大小。
二、什么是运行时常量池
运行时常量池是方法区的一部分。字节码文件中的常量池经过类加载子系统被加载到方法区后形成的结构就称为运行时常量池。
运行时常量池中包含多种不同的常量,包含编译器就已经明确的数值字面量,也包含到运行期解析后才能获得的方法或者字段引用。相较于常量池,它具有动态性。如"CSDN".intern(),如果在常量池中没有"CSDN"时,就会在运行时常量池中创建。