- 从jdk7开始,字符串常量池从Perm区,也就是永久代转移到了堆中,堆是一块很大的空间,这样做的好处是防止永久代出现OOM(内存溢出)。由于jdk7对于字符串常量池的改变,所以同一份代码在不同的jdk环境下可能会有不同的运行结果,主要在于在jdk6及之前,永久代中的字符串常量池存储的是字符串对象。从jdk7开始,堆中的字符串常量池不仅可以存储字符串对象,也能存储堆中字符串对象的地址。但要注意的是,代码
String s = new String("1");不管在jdk6中还是jdk7中,这段代码都是创建了两个对象,且这两个对象的内存地址不同。"1"是字符串常量池中的字符串对象,在运行 new String("1")时,把字符串常量池中的字符串"1"复制到堆中。 - 对于 “从jdk7开始,堆中的字符串常量池不仅可以存储字符串对象,也能存储堆中字符串对象的地址” 这句话,可以举例说明
//测试环境为jdk8
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
首先第一行代码创建了两个对象,一个堆中的字符串对象,即通过StringBuilder的append拼接最后调用StringBuilder重写的toString方法返回了new String("11"),另外一个对象为字符串常量池中的对象"1",s3这个引用保存了了堆中的字符串对象"11"的内存地址,s3.intern()执行时,发现字符串常量池中并没有"11",因此在字符串常量池中保存了一份指向堆中"11"地址的引用,s4指向常量池中的"11",而常量池中的"11"实际上保存的是堆中"11"对象的内存地址,因此s3与s4指向了同一个内存地址,所以结果为true。需要注意的是,假如上面代码的测试环境为jdk6,结果又会不同,原因很简单,在s3.intern()执行时,同样发现字符串常量池中没有"11",jdk6会直接在字符串常量池中生成一份"11"对象,在这种情况下,s3指向的是堆中的"11",而s4指向的是新生成的字符串常量池中的"11",他们之间的内存地址并不相同。假设在jdk8的环境下,将中间两行代码互换位置,结果会是false,因为String s4 = "11";这段代码会直接在字符串常量池中生成一个字符串对象"11",这个对象不是由s3.intern()而来的,所以堆中的"11"与常量池中的"11"并不相同。
- 需要注意的地方
- 以字面量形式出现的字符串,就是在字符串常量池中。
- 像代码
String s3 = new String("1") + new String("1");,StringBuilder中的toString()方法的调用并不会在字符串常量池中生成字符串对象。
- 参考了美团的文章《深入解析String#intern》