JLS
JLS 7 3.10.5对其进行了定义并举了一个实际的例子:
此外,字符串字面量总是引用 String 类的同一个实例。这是因为字符串字面量 - 或者更一般地说,作为常量表达式值的字符串(第 15.28 节) - 是“内部的”,以便使用 String.intern 方法共享唯一实例。
示例 3.10.5-1。字符串字面量
由编译单元组成的程序(§7.3):
package testPackage;
class Test {
public static void main(String[] args) {
String hello = "Hello", lo = "lo";
System.out.print((hello == "Hello") + " ");
System.out.print((Other.hello == hello) + " ");
System.out.print((other.Other.hello == hello) + " ");
System.out.print((hello == ("Hel"+"lo")) + " ");
System.out.print((hello == ("Hel"+lo)) + " ");
System.out.println(hello == ("Hel"+lo).intern());
}
}
class Other { static String hello = "Hello"; }
和编译单元:
package other;
public class Other { public static String hello = "Hello"; }
产生输出:
true true true true false true
JVMS
JVMS 7 5.1 says 说实习是通过专用的CONSTANT_String_info 结构神奇而高效地实现的(与大多数其他具有更通用表示的对象不同):
字符串字面量是对 String 类实例的引用,它派生自类或接口的二进制表示形式的 CONSTANT_String_info 结构(第 4.4.3 节)。 CONSTANT_String_info 结构给出了构成字符串文字的 Unicode 代码点序列。
Java 编程语言要求相同的字符串文字(即包含相同代码点序列的文字)必须引用 String 类的相同实例(JLS §3.10.5)。此外,如果对任何字符串调用 String.intern 方法,则结果是对同一类实例的引用,如果该字符串以文字形式出现,则会返回该类实例。因此,以下表达式的值必须为 true:
("a" + "b" + "c").intern() == "abc"
为了派生字符串文字,Java 虚拟机检查 CONSTANT_String_info 结构给出的代码点序列。
如果先前已在包含与 CONSTANT_String_info 结构给出的相同的 Unicode 代码点序列的类 String 的实例上调用方法 String.intern,则字符串文字派生的结果是对该类的引用String 类的相同实例。
否则,将创建一个 String 类的新实例,其中包含 CONSTANT_String_info 结构给出的 Unicode 代码点序列;对该类实例的引用是字符串文字派生的结果。最后调用新String实例的intern方法。
字节码
让我们反编译一些 OpenJDK 7 字节码,看看实习的实际效果。
如果我们反编译:
public class StringPool {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println(a);
System.out.println(b);
System.out.println(a == c);
}
}
我们在常量池上:
#2 = String #32 // abc
[...]
#32 = Utf8 abc
和main:
0: ldc #2 // String abc
2: astore_1
3: ldc #2 // String abc
5: astore_2
6: new #3 // class java/lang/String
9: dup
10: ldc #2 // String abc
12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
15: astore_3
16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
19: aload_1
20: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
23: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
26: aload_2
27: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: aload_3
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
注意方法:
-
0 和 3:加载相同的 ldc #2 常量(文字)
-
12:创建了一个新的字符串实例(以#2 作为参数)
-
35: a 和 c 与 if_acmpne 比较为常规对象
常量字符串的表示在字节码上相当神奇:
上面的 JVMS 引用似乎是说,每当指向的 Utf8 相同时,ldc 就会加载相同的实例。
我对字段做了类似的测试,并且:
结论:字符串池有直接字节码支持,内存表示效率高。
奖励:将其与不支持直接字节码的Integer pool 进行比较(即没有CONSTANT_String_info 模拟)。