运行时常量池
概念: 属于方法区的一部分 Class 文件中含有常量池表(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容加载之后放到方法区的运行时常量池中。
(1)Class 常量池 (又称常量池) : 主要用于存放两大类常量: 字面量(Literal) 和符号引用量(Symbolic References)。
字面量相当于 Java 语言层面常量的概念, 如文本字符串,声明为 final 的常量值等;
符号引用则属于编译原理方面的概念,包括了三种类型的常量: 类和接口的全限定名、字段名称和描述符、方法名称和描述符。
(2)运行时常量池: 当类加载到内存中后,jvm 会将 class 常量池中的内容存放到运行时常量中。经过解析(resolve)之后,把符号引用替换为直接引用,解析的过程去查询全局字符串池,也就是 StringTable,来保证运行时常量池所引用的字符串与全剧字符串池中所引用的一致。
(3)字符串常量池(String 常量池,串池,StringTable):类似的 Integer、Long 常量池,属于运行时常量池的一部分(VM 中有且只有一个,被所有类共享),目的是为了分担运行时常量池的工作。
JDK1.8 之后常量池在 元空间中属于本地内存,但 StringTable 在堆 heap 中
特点
动态性: 相对于 Class 文件的常量池来说,具有动态性, 并非预置入 Class 文件中常量池的内容才能进行方法区的运行时常量池,在运行期间也是可以将新的常量放入池中,例如 String 的 intern() 方法。
运行时常量池时方法区的一部分,也会存在 OutOfMemoryError 异常,但是由于 JDK1.8 后的方法区存放在元空间中,所以不容易出现 OutOfMemoryError 异常。
Demo:
public class Demo04 {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b";//S3 在编译时期就确定了结果为 “ab”(编译优化)
String s4 = "ab";
String s5 = s1 + s2;
System.out.println(s3 == s4);
System.out.println(s3 == s5);
System.out.println(s4 == s5);
}
}
结果:true false false
String 比较对象时比较的是地址。
使用 javap 反编译后
可以看到
s3 与 s4 这些都是从常量池的 #11 中取的 ab 地址是同一个
后面的使用 StringBuilder 的 append 方法拼接。StringBuilder 是个对象,存在堆中与常量池的地址不同。
同样思考:
String s1 = new String(“a”)+ new String(“b”);
String s2 = s1.intern();
String s3=”ab”;
s1==s2
s1==s3
s2==”ab”
的结果
总结:
(1) 常量池中的字符串仅仅是个符号, 第一次用到时才会变为对象。
(2) 利用串池机制,优化重复创建字符串。
(3) 字符串变量拼接原理 StringBuilder append(JDK1.8)。
(4) 字符串常量拼接原理时编译优化。
(5) 可以使用 intern 方法,主动将串池中还没放入的字符串对象放入串池中。