本文已参与「新人创作礼」活动,一起开启掘金创作之路。
什么是运行时常量池
- 常量池 : 常量池是 . class文件中的,可以看作一张表(由二进制字节码组成),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息
- 运行时常量池 : 当该类被加载以后 , 它的常量池信息就会放入运行时常量池 , 并把里面的符号地址变为真实地址
StringTable
- StringTable在堆中
我们来讨论以下代码的执行周期:
字符串定义
public class StringTableStudy {
public static void main(String[] args) {
String a = "a";
String b = "b";
String ab = "ab";
}
}
- 当程序运行时,开始加载运行时常量池,此时,a、b、ab这些都只是常量池中的符号,并不是java的String对象
- 当程序执行到String a = "a"; 时,才会把该符号变成 "a"字符串对象,这种行为是懒惰的
- 将字符串对象加载到StringTable中(也叫串池)
- 以上机制,可以避免重复创建字符串对象
字符串变量拼接
我们来讨论字符串变量拼接创建字符串的执行周期
public class StringTableStudy {
public static void main(String[] args) {
String a = "a";
String b = "b";
String ab = "ab";
//拼接字符串对象来创建新的字符串
String c = a+b;
}
}
- 通过拼接的方式(+号)来创建字符串的过程是使用StringBuilder方法来做的,其过程就是StringBuilder().append(“a”).append(“b”).toString()
- 而StringBuilder的toString方法就是 new 一个String对象(会在堆生成)
- 因此,当我们判断 c == ab 时,其实是不等的,因为一个在堆内存中,另一个在串池中
字符串常量拼接
public class StringTableStudy {
public static void main(String[] args) {
String ab = "ab";
String d = "a" + "b";
}
}
- 使用拼接字符串常量的方法来创建新的字符串时,因为内容是常量,javac在编译期会进行优化,结果已在编译期就确定结果为"ab"
- 此时,如果串池中存在"ab",则直接返回
intern方法
public class Main {
public static void main(String[] args) {
String str = new String("a") + new String("b");
//执行完后:StringTable={"a","b"} ,str则是以“ab”的形式存在于堆内存之中
System.out.println(str == “ab”); // 串池中并没有“ab”,返回false
// 调用intern方法,将“ab”放入串池
String str2 = str.intern();
System.out.println(str2 == “ab”); // 返回true
}
}
字符串对象的intern方法,会将该字符串对象尝试放入到串池中
- 这时串池中没有"ab",则会放入到串池中;如果有,则不放入
- 无论放入是否成功,都会返回串池中的字符串对象
intern在JDK6、7版本中的区别?
StringTable调优
- 因为StringTable是由HashTable实现的,所以可以适当增加HashTable桶的个数,来减少字符串放入串池所需要的时间
- 考虑是否需要将字符串对象入池:可以通过intern方法减少重复入池
StringTable为什么要调整到堆中?
- 因为永久代的回收效率很低(当触发Full GC才会,而Full GC是当老年代、永久代空间不足才触发),但是实际开发中会有大量字符串被创建、需要回收,于是移动到堆中