JVM-运行时常量池

59 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

什么是运行时常量池

  • 常量池 常量池是 . class文件中的,可以看作一张表(由二进制字节码组成),虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息
  • 运行时常量池 当该类被加载以后 它的常量池信息就会放入运行时常量池 并把里面的符号地址变为真实地址

StringTable

  • StringTable在堆中

我们来讨论以下代码的执行周期:

字符串定义

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a"; 
		String b = "b";
		String ab = "ab";
	}
}
  1. 当程序运行时,开始加载运行时常量池,此时,a、b、ab这些都只是常量池中的符号,并不是java的String对象
  2. 当程序执行到String a = "a"; 时,才会把该符号变成 "a"字符串对象,这种行为是懒惰的
  3. 将字符串对象加载到StringTable中(也叫串池)
  4. 以上机制,可以避免重复创建字符串对象
字符串变量拼接

我们来讨论字符串变量拼接创建字符串的执行周期

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		//拼接字符串对象来创建新的字符串
		String c = a+b; 
	}
}
  1. 通过拼接的方式(+号)来创建字符串的过程是使用StringBuilder方法来做的,其过程就是StringBuilder().append(“a”).append(“b”).toString()
  2. 而StringBuilder的toString方法就是 new 一个String对象(会在堆生成)
  3. 因此,当我们判断 c == ab 时,其实是不等的,因为一个在堆内存中,另一个在串池中
字符串常量拼接
public class StringTableStudy {
	public static void main(String[] args) {
        String ab = "ab";
		String d = "a" + "b";
	}
}
  1. 使用拼接字符串常量的方法来创建新的字符串时,因为内容是常量,javac在编译期会进行优化,结果已在编译期就确定结果为"ab"
  2. 此时,如果串池中存在"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是当老年代、永久代空间不足才触发),但是实际开发中会有大量字符串被创建、需要回收,于是移动到堆中