目录
JVM中的常量池
一、class常量池
Class常量池可以理解为是Class文件中的资源仓库。Class文件中除了包含类的版本、字段、方法、接口等信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic Reference)。
public class Test2 {
public static void main(String[] args) {
int a = 1;
int b = 2;
String c = "abc";
String d = "efg";
}
}
/**
使用javap -v Test2.class 查看字节码文件
Classfile /opt/project/myself/interView/out/production/interView/com/liheng/buffer/Test2.class
Last modified 2021-12-21; size 802 bytes
MD5 checksum 6b4615b4259196c2745e402728b14cf3
Compiled from "Test2.java"
public class com.liheng.buffer.Test2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#29 // java/lang/Object."<init>":()V
#2 = Class #30 // java/lang/String
#3 = String #31 // adc
#4 = Methodref #2.#32 // java/lang/String."<init>":(Ljava/lang/String;)V
#5 = Class #33 // java/lang/StringBuilder
#6 = Methodref #5.#29 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#34 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = String #35 // d
#9 = Methodref #5.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#10 = Fieldref #37.#38 // java/lang/System.out:Ljava/io/PrintStream;
#11 = Methodref #39.#40 // java/io/PrintStream.println:(Ljava/lang/String;)V
#12 = Class #41 // com/liheng/buffer/Test2
#13 = Class #42 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/liheng/buffer/Test2;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 s
#26 = Utf8 Ljava/lang/String;
#27 = Utf8 SourceFile
#28 = Utf8 Test2.java
#29 = NameAndType #14:#15 // "<init>":()V
#30 = Utf8 java/lang/String
#31 = Utf8 adc
#32 = NameAndType #14:#43 // "<init>":(Ljava/lang/String;)V
#33 = Utf8 java/lang/StringBuilder
#34 = NameAndType #44:#45 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#35 = Utf8 d
#36 = NameAndType #46:#47 // toString:()Ljava/lang/String;
#37 = Class #48 // java/lang/System
#38 = NameAndType #49:#50 // out:Ljava/io/PrintStream;
#39 = Class #51 // java/io/PrintStream
#40 = NameAndType #52:#43 // println:(Ljava/lang/String;)V
#41 = Utf8 com/liheng/buffer/Test2
#42 = Utf8 java/lang/Object
#43 = Utf8 (Ljava/lang/String;)V
#44 = Utf8 append
#45 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#46 = Utf8 toString
#47 = Utf8 ()Ljava/lang/String;
#48 = Utf8 java/lang/System
#49 = Utf8 out
#50 = Utf8 Ljava/io/PrintStream;
#51 = Utf8 java/io/PrintStream
#52 = Utf8 println
{
public com.liheng.buffer.Test2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/liheng/buffer/Test2;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String adc
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: new #5 // class java/lang/StringBuilder
13: dup
14: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
17: aload_1
18: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: ldc #8 // String d
23: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_1
30: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: return
LineNumberTable:
line 10: 0
line 11: 10
line 12: 30
line 13: 37
LocalVariableTable:
Start Length Slot Name Signature
0 38 0 args [Ljava/lang/String;
10 28 1 s Ljava/lang/String;
}
*/
上面Constant pool中就是常量池信息,主要存放两大类:字面量和符号引用
这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装入内存就变成运行时常量,对应的符号引用在程序加载或者运行时会被转变为被加载内存区域的代码的直接引用,也就是动态链接了。
1.1 字面量
字面量就是指由字母、数字等构成的字符串或者数值常量。字面量只可以右值出现,所谓右值是指等号右边的值。
int a = 1 //a为左值 1为右值
1.2 符号引用
符号引用主要包括以下三类常量:
-
类和接口的权限定名
-
字段的名称和描述符
-
方法的名称和描述符
int a = 1;
int b = 2;
//a和b就是一种符号引用。
二、字符串常量池
2.1字符串常量池的设计思想
1、字符串的分配和其他的对象一样,耗费高昂的时间与空间代价,最为最基础的数据类型,大量频繁的创建字符串,会极大的影响程序的性能。
2、JVM为了提高性能和内存开销,在实例化字符串常量的时候做了一些优化。
-
为字符串开辟一个字符串常量池,类似于缓存区
-
创建字符串常量时,首先查询字符串常量池是否存在该字符串
-
存在该字符串,返回引用实例,不存在,实例化该字符串被放入池中
三、三种创建字符串的方式(jdk1.7及以上版本)
-
直接赋值字符串
String s = "liheng";//s指向常量池中的引用
这种方式创建的字符串对象,只会在常量池中。因为会有"liheng"这个字面量,所以JVM会先去常量池中通过equals(key)方法判断是否有相同的对象。
如果有则返回字符串常量池中的引用。反之,则在常量池中创建一个新对象,并返回其引用。
-
new String()
这种方式创建会保证在常量池中和堆中都会有这个对象,没有就创建,最后返回堆中的引用。
因为有"liheng"这个字面量,所以会先去检查字符串常量池中是否有这个字符串。不存在现在字符串常量池中创建一个字符串对象;再去堆内存中创建一个字符串对象"liheng"。反之,存在的话,就去对内存中创建一个字符串对象"liheng"。
最后返回的是对内存的中的对象。
-
intern方法
String s = new String("liheng");
String s1 = s.intern();
String中的intern()方法是一个native方法。当调用intern时,如果池中已经包含了一个等于此String的字符串(equals(key)判断),则返回池中的字符串,否则,将intern返回引用指向当前字符串s。(jdk1.6需要将s复制到常量池中)。
四、字符串常量池的位置
-
jdk1.6及之前:有永久代,运行时常量池在永久代,运行时常量池包含字符串常量池。
-
jdk1.7有永久代,但是已经在去永久代,字符串常量池从永久代里的运行时常量池分离到堆里。
-
jdk1.8及以后,无永久代,运行时常量池在元空间,字符串常量池依然在堆中