常量池:
就是*.class 文件中的一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息
运行时常量池:
常量池是 *.class 文件中的,当该类被加载以后,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
StringTable 的位置
jdk1.6 StringTable 位置是在永久代中,1.8 StringTable 位置是在堆中。
因为永久代的垃圾回收效率不高,串池太多,常量池太大会撑爆,会报永久代内存不足。
所以1.8将串池放入堆中,如果串池太大会报堆内存不足
StringTable – 串池
- 常量池中的信息,都会被加载到运行时常量池中。这时其中的字符串仅是符号,只有在被用到时才会转化为对象(懒加载)。
- ldc会从运行时常量池中加载符号,从而变成字符串对象。并将其放入 串池 ,存储机制是哈希表,也就是不重复,传池中没有才放入
利用串池 哈希存储的机制,来避免重复创建字符串对象
- 字符串变量拼接a+b的原理是StringBuilder(1.8) //相当于 new StringBuilder().append(“a”).apend(“b”).toString(); == new String(“ab”) 。。。。在编译期间不能确定,所以在运行期间创建为堆中对象。
- 字符串常量拼接的原理是编译器优化: 已经再编译期间确定为不变的字符常量,所以不会再变了。就放入常量池
- intern方法 1.8
- 调用字符串对象的 intern 方法,会将该字符串对象尝试放入到串池中
- 1.8中 如果有该字符串对象,则放入失败,,如果串池中没有该字符串对象,则放入成功(同一个对象)
无论放入是否成功,都会返回串池中的字符串对象
注意:此时如果调用 intern 方法成功,堆内存与串池中的字符串对象是同一个对象;如果失败,则不是同一个对象 - 1.6中 如果有该字符串对象,则放入失败,,如果没有,则会将对象复制一份,放入串池中(两个不同对象)
例1
public class Main {
public static void main(String[] args) {
// "a" "b" 被放入串池中,str 则存在于堆内存之中
String str = new String("a") + new String("b");
// 调用 str 的 intern 方法,这时串池中没有 "ab" ,则会将该字符串对象放入到串池中,此时堆内存与串池中的 "ab" 是同一个对象
String st2 = str.intern();
// 给 str3 赋值,因为此时串池中已有 "ab" ,则直接将串池中的内容返回
String str3 = "ab";
// 因为堆内存与串池中的 "ab" 是同一个对象,所以以下两条语句打印的都为 true
System.out.println(str == st2);
System.out.println(str == str3);
}
}
例2:
public class Main {
public static void main(String[] args) {
// 此处创建字符串对象 "ab" ,因为串池中还没有 "ab" ,所以将其放入串池中
String str3 = "ab";
// "a" "b" 被放入串池中,str 则存在于堆内存之中
String str = new String("a") + new String("b");
// 此时因为在创建 str3 时,"ab" 已存在与串池中,所以放入失败,但是会返回串池中的 "ab"
String str2 = str.intern();
// false 内存中 不等于 串池中
System.out.println(str == str2);
// false 内存中 不等于 传池中
System.out.println(str == str3);
// true 串池中 等于 串池中
System.out.println(str2 == str3);
}
}
面试题: