聊聊java String的intern

21次阅读

共计 11696 个字符,预计需要花费 30 分钟才能阅读完成。


本文主要研究一下 java String 的 intern
String.intern()
java.base/java/lang/String.java
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence,
Constable, ConstantDesc {

//……

/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java&trade; Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
* @jls 3.10.5 String Literals
*/
public native String intern();

//……

}

当调用 intern 方法时,如果常量池已经包含一个 equals 此 String 对象的字符串,则返回池中的字符串
当调用 intern 方法时,如果常量池没有一个 equals 此 String 对象的字符串,将此 String 对象添加到池中,并返回此 String 对象的引用 (即 intern 方法返回指向 heap 中的此 String 对象引用)
所有 literal strings 及 string-valued constant expressions 都是 interned 的

实例
基于 jdk12
StringExistInPoolBeforeIntern
public class StringExistInPoolBeforeIntern {

public static void main(String[] args){
String stringObject = new String(“tomcat”);
//NOTE 在 intern 之前,string table 已经有了 tomcat,因而 intern 返回 tomcat,不会指向 stringObject
stringObject.intern();
String stringLiteral = “tomcat”;
System.out.println(stringObject == stringLiteral); //false
}
}

tomcat 这个 literal string 是 interned 过的,常量池没有 tomcat,因而添加到常量池,常量池有个 tomcat;另外由于 stringObject 是 new 的,所以 heap 中也有一个 tomcat,而此时它指向 heap 中的 tomcat
stringObject.intern() 返回的是 heap 中常量池的 tomcat;stringLiteral 是 tomcat 这个 literal string,由于常量池已经有该值,因而 stringLiteral 指向的是 heap 中常量池的 tomcat
此时 stringObject 指向的是 heap 中的 tomcat,而 stringLiteral 是 heap 中常量池的 tomcat,因而二者不等,返回 false

StringNotExistInPoolBeforeIntern
public class StringNotExistInPoolBeforeIntern {

public static void main(String[] args){
String stringObject = new String(“tom”) + new String(“cat”);
//NOTE 在 intern 之前,string table 没有 tomcat,因而 intern 指向 stringObject
stringObject.intern();
String stringLiteral = “tomcat”;
System.out.println(stringObject == stringLiteral); //true
}
}

tom 及 cat 这两个 literal string 是 interned 过的,常量池没有 tom 及 cat,因而添加到常量池,常量池有 tom、cat;另外由于 stringObject 是 new 出来的,是 tom 及 cat 二者 concat,因而 heap 中有一个 tomcat
stringObject 的 intern 方法执行的时候,由于常量池中没有 tomcat,因而添加到常量池,intern() 返回的是指向 heap 中的 tomcat 的引用;stringLiteral 是 tomcat 这个 literal string,由于 stringObject.intern() 已经将 tomcat 添加到常量池了并指向 heap 中的 tomcat 的引用,所以 stringLiteral 返回的是指向 heap 中的 tomcat 的引用
由于 stringLiteral 返回的是指向 heap 中的 tomcat 的引用,其实就是 stringObject,因而二者相等,返回 true

javap
基于 jdk12
StringExistInPoolBeforeIntern
javac src/main/java/com/example/javac/StringExistInPoolBeforeIntern.java
javap -v src/main/java/com/example/javac/StringExistInPoolBeforeIntern.class

Last modified 2019 年 4 月 6 日; size 683 bytes
MD5 checksum 207635ffd7560f1df24b98607e2ca7db
Compiled from “StringExistInPoolBeforeIntern.java”
public class com.example.javac.StringExistInPoolBeforeIntern
minor version: 0
major version: 56
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #8 // com/example/javac/StringExistInPoolBeforeIntern
super_class: #9 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #9.#21 // java/lang/Object.”<init>”:()V
#2 = Class #22 // java/lang/String
#3 = String #23 // tomcat
#4 = Methodref #2.#24 // java/lang/String.”<init>”:(Ljava/lang/String;)V
#5 = Methodref #2.#25 // java/lang/String.intern:()Ljava/lang/String;
#6 = Fieldref #26.#27 // java/lang/System.out:Ljava/io/PrintStream;
#7 = Methodref #18.#28 // java/io/PrintStream.println:(Z)V
#8 = Class #29 // com/example/javac/StringExistInPoolBeforeIntern
#9 = Class #30 // java/lang/Object
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 StackMapTable
#17 = Class #31 // “[Ljava/lang/String;”
#18 = Class #32 // java/io/PrintStream
#19 = Utf8 SourceFile
#20 = Utf8 StringExistInPoolBeforeIntern.java
#21 = NameAndType #10:#11 // “<init>”:()V
#22 = Utf8 java/lang/String
#23 = Utf8 tomcat
#24 = NameAndType #10:#33 // “<init>”:(Ljava/lang/String;)V
#25 = NameAndType #34:#35 // intern:()Ljava/lang/String;
#26 = Class #36 // java/lang/System
#27 = NameAndType #37:#38 // out:Ljava/io/PrintStream;
#28 = NameAndType #39:#40 // println:(Z)V
#29 = Utf8 com/example/javac/StringExistInPoolBeforeIntern
#30 = Utf8 java/lang/Object
#31 = Utf8 [Ljava/lang/String;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 (Ljava/lang/String;)V
#34 = Utf8 intern
#35 = Utf8 ()Ljava/lang/String;
#36 = Utf8 java/lang/System
#37 = Utf8 out
#38 = Utf8 Ljava/io/PrintStream;
#39 = Utf8 println
#40 = Utf8 (Z)V
{
public com.example.javac.StringExistInPoolBeforeIntern();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object.”<init>”:()V
4: return
LineNumberTable:
line 8: 0

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String tomcat
6: invokespecial #4 // Method java/lang/String.”<init>”:(Ljava/lang/String;)V
9: astore_1
10: aload_1
11: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String;
14: pop
15: ldc #3 // String tomcat
17: astore_2
18: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_1
22: aload_2
23: if_acmpne 30
26: iconst_1
27: goto 31
30: iconst_0
31: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
34: return
LineNumberTable:
line 11: 0
line 13: 10
line 14: 15
line 15: 18
line 16: 34
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 30
locals = [class “[Ljava/lang/String;”, class java/lang/String, class java/lang/String]
stack = [class java/io/PrintStream]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [class “[Ljava/lang/String;”, class java/lang/String, class java/lang/String]
stack = [class java/io/PrintStream, int]
}
SourceFile: “StringExistInPoolBeforeIntern.java”
可以看到常量池有个 tomcat
StringNotExistInPoolBeforeIntern
javac src/main/java/com/example/javac/StringNotExistInPoolBeforeIntern.java
javap -v src/main/java/com/example/javac/StringNotExistInPoolBeforeIntern.class

Last modified 2019 年 4 月 6 日; size 1187 bytes
MD5 checksum 6d173f303b61b8f5826e54bb6ed5157c
Compiled from “StringNotExistInPoolBeforeIntern.java”
public class com.example.javac.StringNotExistInPoolBeforeIntern
minor version: 0
major version: 56
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #11 // com/example/javac/StringNotExistInPoolBeforeIntern
super_class: #12 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 3
Constant pool:
#1 = Methodref #12.#24 // java/lang/Object.”<init>”:()V
#2 = Class #25 // java/lang/String
#3 = String #26 // tom
#4 = Methodref #2.#27 // java/lang/String.”<init>”:(Ljava/lang/String;)V
#5 = String #28 // cat
#6 = InvokeDynamic #0:#32 // #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#7 = Methodref #2.#33 // java/lang/String.intern:()Ljava/lang/String;
#8 = String #34 // tomcat
#9 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #21.#37 // java/io/PrintStream.println:(Z)V
#11 = Class #38 // com/example/javac/StringNotExistInPoolBeforeIntern
#12 = Class #39 // java/lang/Object
#13 = Utf8 <init>
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 StackMapTable
#20 = Class #40 // “[Ljava/lang/String;”
#21 = Class #41 // java/io/PrintStream
#22 = Utf8 SourceFile
#23 = Utf8 StringNotExistInPoolBeforeIntern.java
#24 = NameAndType #13:#14 // “<init>”:()V
#25 = Utf8 java/lang/String
#26 = Utf8 tom
#27 = NameAndType #13:#42 // “<init>”:(Ljava/lang/String;)V
#28 = Utf8 cat
#29 = Utf8 BootstrapMethods
#30 = MethodHandle 6:#43 // REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#31 = String #44 // \u0001\u0001
#32 = NameAndType #45:#46 // makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#33 = NameAndType #47:#48 // intern:()Ljava/lang/String;
#34 = Utf8 tomcat
#35 = Class #49 // java/lang/System
#36 = NameAndType #50:#51 // out:Ljava/io/PrintStream;
#37 = NameAndType #52:#53 // println:(Z)V
#38 = Utf8 com/example/javac/StringNotExistInPoolBeforeIntern
#39 = Utf8 java/lang/Object
#40 = Utf8 [Ljava/lang/String;
#41 = Utf8 java/io/PrintStream
#42 = Utf8 (Ljava/lang/String;)V
#43 = Methodref #54.#55 // java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#44 = Utf8 \u0001\u0001
#45 = Utf8 makeConcatWithConstants
#46 = Utf8 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#47 = Utf8 intern
#48 = Utf8 ()Ljava/lang/String;
#49 = Utf8 java/lang/System
#50 = Utf8 out
#51 = Utf8 Ljava/io/PrintStream;
#52 = Utf8 println
#53 = Utf8 (Z)V
#54 = Class #56 // java/lang/invoke/StringConcatFactory
#55 = NameAndType #45:#60 // makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#56 = Utf8 java/lang/invoke/StringConcatFactory
#57 = Class #62 // java/lang/invoke/MethodHandles$Lookup
#58 = Utf8 Lookup
#59 = Utf8 InnerClasses
#60 = Utf8 (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
#61 = Class #63 // java/lang/invoke/MethodHandles
#62 = Utf8 java/lang/invoke/MethodHandles$Lookup
#63 = Utf8 java/lang/invoke/MethodHandles
{
public com.example.javac.StringNotExistInPoolBeforeIntern();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object.”<init>”:()V
4: return
LineNumberTable:
line 8: 0

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String tom
6: invokespecial #4 // Method java/lang/String.”<init>”:(Ljava/lang/String;)V
9: new #2 // class java/lang/String
12: dup
13: ldc #5 // String cat
15: invokespecial #4 // Method java/lang/String.”<init>”:(Ljava/lang/String;)V
18: invokedynamic #6, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
23: astore_1
24: aload_1
25: invokevirtual #7 // Method java/lang/String.intern:()Ljava/lang/String;
28: pop
29: ldc #8 // String tomcat
31: astore_2
32: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
35: aload_1
36: aload_2
37: if_acmpne 44
40: iconst_1
41: goto 45
44: iconst_0
45: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
48: return
LineNumberTable:
line 11: 0
line 13: 24
line 14: 29
line 15: 32
line 16: 48
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 44
locals = [class “[Ljava/lang/String;”, class java/lang/String, class java/lang/String]
stack = [class java/io/PrintStream]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [class “[Ljava/lang/String;”, class java/lang/String, class java/lang/String]
stack = [class java/io/PrintStream, int]
}
SourceFile: “StringNotExistInPoolBeforeIntern.java”
InnerClasses:
public static final #58= #57 of #61; // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #30 REF_invokeStatic java/lang/invoke/StringConcatFactory.makeConcatWithConstants:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
Method arguments:
#31 \u0001\u0001
可以看到常量池有 tom、cat、tomcat
小结

当调用 intern 方法时,如果常量池已经包含一个 equals 此 String 对象的字符串,则返回池中的字符串
当调用 intern 方法时,如果常量池没有一个 equals 此 String 对象的字符串,将此 String 对象添加到池中,并返回此 String 对象的引用 (即 intern 方法返回指向 heap 中的此 String 对象引用)
所有 literal strings 及 string-valued constant expressions 都是 interned 的

doc

浅谈 String 的 intern
Why does String.intern() return different results under JDK8 and JDK9?
How to Initialize and Compare Strings in Java?
Difference between String literal and New String object in Java
聊聊 jvm 的 PermGen 与 Metaspace
Guide to Java String Pool
String Literal Vs String Object in Java

正文完
 0