目錄 一、基本概念 二、存放位置 2.1 JDK1.6及以前 2.2 JDK1.7 2.3 JDK1.8及以后 三、工作原理 3.1 創建字符串常量 3.2 使用new關鍵字創建字符串 四、intern()方法 五、優點 六、字節碼分析 6.1 示例1 6.1.1 代碼示例 6.1.2 字節碼 6.1.3 解析 6.2 示例2 6.3 示例3
一、基本概念
1.1 說明
1.JVM字符串常量池是Java虛擬機(JVM)中一個特殊的內存區域。 2.JVM字符串常量池用于存儲字符串常量。 3.提高性能和減少內存開銷。 4.字符串常量池是JVM用于存儲字符串常量的一個內存區域,避免了相同字符串的重復創建,節省內存空間。
1.2 特點
1.字符串常量池中的字符串對象是不可變的。 2.相同的字符串常量在池中只存儲一份,通過引用共享。
二、存放位置
2.1 JDK1.6及以前
1.字符串常量池存放在永久代中,永久代是非堆內存的一部分,用于存儲類的元數據、常量、靜態變量等。
2.2 JDK1.7
1.字符串常量池從永久代移動到了Java堆中,而運行時常量池保留在永久代中。 2.這一變化為了適應永久代內存限制問題,并提升性能。
2.3 JDK1.8及以后
1.永久代被移除,取而代之的是元空間,字符串常量池仍然位于Java堆中。 2.運行時常量池被移動到元空間。
三、工作原理
3.1 創建字符串常量
1.使用雙引號創建字符串時(String a = “123”; ),JVM會首先在字符串常量池中查找是否已存在該字符串。 2.如果存在,則直接返回池中該字符串的引用。 3.如果不存在,則在常量池中創建該字符串的實例,并返回其引用。
3.2 使用new關鍵字創建字符串
1.使用new關鍵字創建字符串對象(如String str = new String(“abc”);)時,JVM會在堆內存中創建一個新的字符串對象,而不管字符串常量池中是否已存在相同的字符串。 2.如果需要,可以通過調用intern()方法將新創建的字符串對象放入常量池中。
四、intern()方法
4.1 作用
1.intern()方法是String類的一個本地方法。 2.用于將字符串對象添加到字符串常量池中。 3.如果常量池中已經包含了一個等于此String對象的字符串(使用equals(Object)方法確定),則返回代表池中這個字符串的String對象的引用。 4.否則,將此String對象包含的字符串添加到常量池中,并返回此String對象的引用。
五、優點
1.節省內存:通過共享相同的字符串常量,避免了不必要的重復創建。 2.提高性能:減少了對象創建和垃圾回收的開銷。 3.簡化字符串比較:由于字符串常量池中的字符串是唯一的,可以使用==操作符來比較字符串的引用,從而簡化比較操作。
六、字節碼分析
6.1 示例1
6.1.1 代碼示例
@Test
public void test(){String str1 = new String("hello") + new String("world");String str2 = "helloworld";System.out.println(str1 == str2);
}
6.1.2 字節碼
0 new #2 <java/lang/StringBuilder>3 dup4 invokespecial #3 <java/lang/StringBuilder.<init> : ()V>7 new #4 <java/lang/String>
10 dup
11 ldc #5 <hello>
13 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
19 new #4 <java/lang/String>
22 dup
23 ldc #8 <world>
25 invokespecial #6 <java/lang/String.<init> : (Ljava/lang/String;)V>
28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
34 astore_1
35 ldc #10 <helloworld>
37 astore_2
38 getstatic #11 <java/lang/System.out : Ljava/io/PrintStream;>
41 aload_1
42 aload_2
43 if_acmpne 50 (+7)
46 iconst_1
47 goto 51 (+4)
50 iconst_0
51 invokevirtual #12 <java/io/PrintStream.println : (Z)V>
54 return
6.1.3 解析
1. 0 new #2 <java/lang/StringBuilder>: 調用StringBuilder的new方法 2. 3 dup:復制操作數棧棧頂的一個字(通常是對象引用或數據類型值),并將這個字重新壓入棧頂。 3. 4 invokespecial #3 <java/lang/StringBuilder. : ()V>:執行StringBuilder的初始化方法,會消耗操作數棧頂一個字。 4. 7 new #4 <java/lang/String>:new一個String對象,對象的引用壓入操作數棧。 5. 10 dup:復制操作數棧棧頂的一個字(通常是對象引用或數據類型值),并將這個字重新壓入棧頂。 6. 11 ldc #5 :加載棧頂的一個字,即hello。 7. 13 invokespecial #6 <java/lang/String. : (Ljava/lang/String;)V>:初始化String,消耗一個string對象的引用和復制的字。 8. 16 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;> : append操作,將hello追加進來。 9. 19 new #4 <java/lang/String> : new一個String對象,對象的引用壓入操作數棧。 10. 22 dup:復制操作數棧棧頂的一個字(通常是對象引用或數據類型值),并將這個字重新壓入棧頂。 11. 23 ldc #8 : 加載棧頂的一個字,即world。 12. 25 invokespecial #6 <java/lang/String. : (Ljava/lang/String;)V>:初始化String,消耗一個string對象的引用和復制的字。 13. 28 invokevirtual #7 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>: append操作,將world追加進來。 14. 31 invokevirtual #9 <java/lang/StringBuilder.toString : ()Ljava/lang/String;> :調用StringBuilder的toString方法 15. StringBuilder的toString方法中是直接new了一個String對象。 16. 而String str2 = “helloworld”;是常量池的引用。 17. 因此不是同一個內存地址,所以結果是false。
6.2 示例2
6.2.1 代碼示例
@Testpublic void test(){String str1 = new String("hello") + new String("world");str1.intern();String str2 = "helloworld";System.out.println(str1 == str2);}
6.2.2 分析(jdk8)
1.str1是直接new了一個對象,執行intern()方法后,將字符串對象添加到字符串常量池中。 2.String str2 = “helloworld”;會先在字符串常量池中找是否有,如果有則返回其對象的引用。 3.所以結果是true。
6.3 示例3
6.3.1 代碼示例
@Testpublic void test(){String str1 = new String("helloworld") ;String str2 = "helloworld";String intern = str1.intern();System.out.println(str1 == str2);System.out.println(str1 == intern);System.out.println(str2 == intern);}
6.2.2 分析(jdk8)
1.str1在堆上new了一個string對象。 2.str2是將字面量“helloworld”放入字符串常量池中。 3.str1調用intern方法,判斷字符串常量池中有沒有helloworld,發現有,返回了該字符串常量池的引用即str2。 4.此時str1不等于str2。str2和intern是相等的。