JVM内存结构(2)https://developer.aliyun.com/article/1530768
5.5 StringTable
StringTable 特性
- 常量池中的字符串仅是符号,只有在被用到时才会转化为对象
- 利用串池的机制,来避免重复创建字符串对象
- 字符串变量拼接的原理是StringBuilder
- 字符串常量拼接的原理是编译器优化
- 可以使用intern方法,主动将串池中还没有的字符串对象放入串池中
- 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
- 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回
public class StringTableStudy { public static void main(String[] args) { String a = "a"; String b = "b"; String ab = "ab"; } }
常量池中的信息,都会加载到运行时常量池中,这时a b ab都是常量池中的符号,还没有变为java字符串对象
等到执行到具体引用它的代码上
ldc #2
就会找到a这个符号,然后把他变成字符串对象,然后把他作为key在StringTable[]中去找,看有没有取值相同的key,如果没有找到话,就会把这个a这个字符串对象放进去也就是放入串池中
注意:字符串对象的创建都是懒惰的,只有当运行到那一行字符串且在串池中不存在的时候(如 ldc #2)时,该字符串才会被创建并放入串池中。
使用拼接字符串变量对象创建字符串的过程
public class HelloWorld { public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "ab"; String s4=s1+s2;//new StringBuilder().append("a").append("2").toString() new String("ab") System.out.println(s3==s4);//false //结果为false,因为s3是存在于串池之中,s4是由StringBuffer的toString方法所返回的一个对象,存在于堆内存之中 }
反编译后的结果
Code: stack=2, locals=5, args_size=1 0: ldc #2 // String a 2: astore_1 3: ldc #3 // String b 5: astore_2 6: ldc #4 // String ab 8: astore_3 9: new #5 // class java/lang/StringBuilder 12: dup 13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V 16: aload_1 17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String ;)Ljava/lang/StringBuilder; 20: aload_2 21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String ;)Ljava/lang/StringBuilder; 24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/Str ing; 27: astore 4 29: return
通过拼接的方式来创建字符串的过程是:StringBuilder().append(“a”).append(“b”).toString()
最后的toString方法的返回值是一个新的字符串,但字符串的值和拼接的字符串一致,但是两个不同的字符串,一个存在于串池之中,一个存在于堆内存之中
字符串拼接的操作
- 创建一个StringBuilder的对象
- 调用StringBuilder的init 方法
- 把s1加载过来
- 调用append方法
- 拿到s2 参数,调用append方法
- 调用toString方法
在Java中,字符串常量池和堆是不同的内存区域,而字符串的创建方式决定了它们在哪个区域分配内存。
- 字符串常量池: 字符串常量池是专门用于存储字符串常量的区域。当你使用字符串字面量(比如
"abc"
)创建字符串时,Java首先会检查字符串常量池中是否已经存在相同内容的字符串。如果存在,它将返回对现有字符串的引用,而不是创建新的字符串对象。这样做有助于节省内存,因为相同的字符串在常量池中只存储一次。
在你的代码中,String s1 = "a";
、String s2 = "b";
和String s3 = "ab";
这三个字符串都是通过字符串字面量创建的,因此它们都存储在字符串常量池中。 - 堆: 堆是用于存储动态分配对象的内存区域。当你使用
new
关键字创建字符串对象时,该对象会在堆中分配内存。在你的代码中,String s4 = s1 + s2;
这行代码使用了字符串拼接操作,它会创建一个新的字符串对象,其内容是 s1 和 s2 的拼接结果。因为这是在运行时动态生成的,所以会在堆中分配内存。
因此,s1
、s2
、s3
是字符串常量池中的字符串,而 s4
是通过字符串拼接在堆中动态创建的字符串对象。这也是为什么 s3 == s4
的结果可能是 false
,因为它们分别指向不同的内存位置。
使用字符串常量拼接创建字符串
public class HelloWorld { public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "ab"; String s4=s1+s2;//new StringBuilder().a|ppend("a").append("2").toString() new String("ab") String s5="a"+"b"; System.out.println(s5==s3);//true } }
反编译的结果
Code: stack=2, locals=6, args_size=1 0: ldc #2 // String a 2: astore_1 3: ldc #3 // String b 5: astore_2 6: ldc #4 // String ab 8: astore_3 9: new #5 // class java/lang/StringBuilder 12: dup 13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V 16: aload_1 17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String ;)Ljava/lang/StringBuilder; 20: aload_2 21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String ;)Ljava/lang/StringBuilder; 24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/Str ing; 27: astore 4 //ab3初始化时直接从串池中获取字符串 29: ldc #4 // String ab 31: astore 5 33: return
从常量池中直接取出来的ab
符号
这是javac
在编译期的优化,它认为a
和b
都是常量都是确定的,那结果已经编译期间确定为ab
而上面的拼接是变量的拼接,还会变,那么只能动态的拼接使用StringBuilder的方式
intern方法 1.8:
调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中
- 如果串池中没有该字符串对象,则放入成功
- 如果有该字符串对象,则放入失败
无论放入是否成功,都会返回串池中的字符串对象
public class HelloWorld { public static void main(String[] args) { String x = "ab"; String s = new String("a") + new String("b"); String s2 = s.intern();//将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,这两种情况都会把串池中的对象返回 System.out.println(s2 == x);//true System.out.println(s == x);//false } }
intern方法 1.6:
调用字符串对象的intern方法,会将该字符串对象尝试放入到串池中
- 如果串池中没有该字符串对象,会将该字符串对象复制一份(又创建了一个对象),再放入到串池中
- 如果有该字符串对象,则放入失败
无论放入是否成功,都会返回串池中的字符串对象
package com.itcast.itheima.xpp; public class main { public static void main(String[] args) { String s1="a"; String s2="b"; String s3="a"+"b"; String s4=s1+s2; String s5="ab"; String s6=s4.intern(); System.out.println(s3==s4);//false System.out.println(s3==s5);//true System.out.println(s3==s6);//true String x2=new String("c")+new String("d"); String x1="cd"; x2.intern(); System.out.println(x1==x2);//false String x4=new String("e")+new String("f"); x4.intern(); String x3="ef"; System.out.println(x3==x4);//true } }
5.5 StringTable 位置
- JDK1.6 时,StringTable是属于常量池的一部分。
- JDK1.8 以后,StringTable是放在堆中的。