常量池/Class文件常量池
每一个Java类都会编译成一个Class文件。Class文件中会包含一个常量池。如下。常量池中包含:
- 字面量:类的全路径字面值、字段名、字段字面值、方法名、方法入参
- 符号引用:Methodref、Fieldref、Class等。
public class StudyClassFile {
//出现在常量池中
static final int staticInt = 10;
//出现在常量池中
static final String staticString = "abd";
//出现在常量池中
static final String staticFinalString = "staticFinalStringDEF";
//出现在常量池中
final String finalString = "finalStringHHH";
public static void main(String[] args) {
}
//class中的常量池:Methodref、Fieldref、Class、utf-8(类的全路径名、类名、方法名、实际字面量的值、字段名、方法入参路径)
}
> javap -c -verbose StudyClassFile
Classfile
.../com/lim/study/vm/metaspace/StudyClassFile.class
Last modified 2021年3月14日; size 546 bytes
MD5 checksum 66cdef11d40e4bd269566c4ce2d4f559
Compiled from "StudyClassFile.java"
public class com.lim.study.vm.metaspace.StudyClassFile
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #4 // com/lim/study/vm/metaspace/StudyClassFile
super_class: #5 // java/lang/Object
interfaces: 0, fields: 4, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #5.#24 // java/lang/Object."<init>":()V
#2 = String #25 // finalStringHHH
#3 = Fieldref #4.#26 // com/lim/study/vm/metaspace/StudyClassFile.finalString:Ljava/lang/String;
#4 = Class #27 // com/lim/study/vm/metaspace/StudyClassFile
#5 = Class #28 // java/lang/Object
#6 = Utf8 staticInt
#7 = Utf8 I
#8 = Utf8 ConstantValue
#9 = Integer 10
#10 = Utf8 staticString
#11 = Utf8 Ljava/lang/String;
#12 = String #29 // abd
#13 = Utf8 staticFinalString
#14 = String #30 // staticFinalStringDEF
#15 = Utf8 finalString
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 SourceFile
#23 = Utf8 StudyClassFile.java
#24 = NameAndType #16:#17 // "<init>":()V
#25 = Utf8 finalStringHHH
#26 = NameAndType #15:#11 // finalString:Ljava/lang/String;
#27 = Utf8 com/lim/study/vm/metaspace/StudyClassFile
#28 = Utf8 java/lang/Object
#29 = Utf8 abd
#30 = Utf8 staticFinalStringDEF
{
static final int staticInt;
descriptor: I
flags: (0x0018) ACC_STATIC, ACC_FINAL
ConstantValue: int 10
static final java.lang.String staticString;
descriptor: Ljava/lang/String;
flags: (0x0018) ACC_STATIC, ACC_FINAL
ConstantValue: String abd
static final java.lang.String staticFinalString;
descriptor: Ljava/lang/String;
flags: (0x0018) ACC_STATIC, ACC_FINAL
ConstantValue: String staticFinalStringDEF
final java.lang.String finalString;
descriptor: Ljava/lang/String;
flags: (0x0010) ACC_FINAL
ConstantValue: String finalStringHHH
public com.lim.study.vm.metaspace.StudyClassFile();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String finalStringHHH
7: putfield #3 // Field finalString:Ljava/lang/String;
10: return
LineNumberTable:
line 8: 0
line 12: 4
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 18: 0
}
SourceFile: "StudyClassFile.java"
运行时常量池
JVM在读取Class文件的时候,会为每个类创建instanceKlass来记录类的元数据。每个instanceKlass上引用着一个constantPoolOopDesc对象,然后间接引用着一个constantPoolCacheOopDesc对象。前者跟Class文件里记录的常量池的结构类似,而后者是为了让解释器运行得更高效的一个缓存。
参考:请问,jvm实现读取class文件常量池信息是怎样呢? - 讨论 - 高级语言虚拟机 - ITeye群组
instanceKlass 中有一个_contants字段,引用constantPoolOopDesc对象。在这个对象中,各个常量的值是混在一起的,基本上和Class文件一样。只不过,在instanceKlass 之间,符号是共享的,但是Class文件不是。
uft8常量在运行时常量池constantPoolOopDesc中已symbolOopDesc来体现。常量池中只包含了对symbol对象的引用,并没有实际存储。而是通过全局的常量表来体现【全局字符串池】。
另外,运行时常量池中可能会存在UnresolvedClass。正是动态类加载/链接的一个表现。 当这个引用的类没有被该类使用过时,就没有被链接【符号引用】。当被使用过了之后,运行时常量池中的这个外部类的应用会变成一个【直接引用】。
符号引用和直接引用
符号引用通常是用来设计在字符串上的,用文本形式来表示引用关系。而直接引用是JVM所能直接使用的形式。既可以表现为直接指针,也可以是其他形式。
符号引用在进行方法调用的时候,还需要解析符号引用所执行的代码,之后,才能执行。
全局字符串值
字符串的内容是在类的加载完成,准备阶段之后,在堆内生成的字符串的实例对象。然后将对象的引用存储到string pool中。
在HotSpot VM 中string pool 是由StringTable实现的,里面存的是引用。存在里面的对象,表示拥有“驻留字符串”的身份。StringTable在VM中的实例只有一份,被所有的类共享。
举例说明
String str1 = "abc";
String str2 = new String("def");
String str3 = "abc";
String str4 = str2.intern();
String str5 = "def";
System.out.println(str1 == str3);//true
System.out.println(str2 == str4);//false
System.out.println(str4 == str5);//true
其中: string1和string3的"abc"值应该在class文件常量池中就是一个String类型。
加载到运行时常量池后,在未解析之前,str1,str3指向了两个符号引用。解析时,会去查询全局字符串。会查到同一个对象,所以str1==str3
str2会因为new操作符,导致创建一个新的对象实例,和之前加载到全局字符池中的不是一个。因此str2≠str5≠str4
intern()函数的作用是:如果全局字符池中没有这个字符串的引用,则将其加入进去。如果有,则返回全局字符池中的引用。因此str4==str5