常量池
是.class文件的常量池,也可以理解爲一張表,虛擬機指令根據這張常量表找到要執行的類名,方法名,參數類型,字面量等信息
運行時常量池
常量池是*.class文件中的,當該類被加載,它的常量池信息就會放入運行時常量池,並把裏面的符號地址變爲真實地址
常量池只有類文件在編譯的時候纔會產生,而且是存儲在類文件中的。而運行時常量池是在方法區,而且可在JVM運行期間動態向運行時常量池中寫入數據。
字符串常量池(string pool)
字符串常量池裏的內容是在類加載完成,經過驗證,準備階段之後在堆中生成字符串對象實例,然後將該字符串對象實例的引用值存到string pool中(記住:string pool中存的是引用值而不是具體的實例對象,具體的實例對象是在堆中開闢的一塊空間存放的)。string pool在每個HotSpot VM的實例只有一份,被所有的類共享。在jdk1.8後,將String常量池放到了堆中。
String table還存在一個hash表的特性,裏面不存在相同的兩個字符串,默認容量爲1009。當字符串常量池中的存儲比較多的字符串時,會導致hash衝突,從而每個節點形成長長的鏈表,導致性能下降。所以在使用字符串常量池時,一定要控制容量。
一道面試題
package com.example;
public class Test {
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);
System.out.println(s3 == s5);
System.out.println(s3 == s6);
}
}
輸出的結果是:false true true
代碼分析
我們使用javap -v 類名命令反編譯我們的類
貼上我的反編譯類
Classfile /D:/myproject/cloud-parent/student-provider-1001/target/classes/com/example/Test.class
Last modified 2020-5-4; size 1107 bytes
MD5 checksum c61895056cf3de488b1ad949bccf93d2
Compiled from "Test.java"
public class com.example.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #13.#38 // java/lang/Object."<init>":()V
#2 = String #39 // a
#3 = String #40 // b
#4 = String #41 // ab
#5 = Class #42 // java/lang/StringBuilder
#6 = Methodref #5.#38 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#43 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#44 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Methodref #45.#46 // java/lang/String.intern:()Ljava/lang/String;
#10 = Fieldref #47.#48 // java/lang/System.out:Ljava/io/PrintStream;
#11 = Methodref #49.#50 // java/io/PrintStream.println:(Z)V
#12 = Class #51 // com/example/Test
#13 = Class #52 // java/lang/Object
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 LocalVariableTable
#19 = Utf8 this
#20 = Utf8 Lcom/example/Test;
#21 = Utf8 main
#22 = Utf8 ([Ljava/lang/String;)V
#23 = Utf8 args
#24 = Utf8 [Ljava/lang/String;
#25 = Utf8 s1
#26 = Utf8 Ljava/lang/String;
#27 = Utf8 s2
#28 = Utf8 s3
#29 = Utf8 s4
#30 = Utf8 s5
#31 = Utf8 s6
#32 = Utf8 StackMapTable
#33 = Class #24 // "[Ljava/lang/String;"
#34 = Class #53 // java/lang/String
#35 = Class #54 // java/io/PrintStream
#36 = Utf8 SourceFile
#37 = Utf8 Test.java
#38 = NameAndType #14:#15 // "<init>":()V
#39 = Utf8 a
#40 = Utf8 b
#41 = Utf8 ab
#42 = Utf8 java/lang/StringBuilder
#43 = NameAndType #55:#56 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#44 = NameAndType #57:#58 // toString:()Ljava/lang/String;
#45 = Class #53 // java/lang/String
#46 = NameAndType #59:#58 // intern:()Ljava/lang/String;
#47 = Class #60 // java/lang/System
#48 = NameAndType #61:#62 // out:Ljava/io/PrintStream;
#49 = Class #54 // java/io/PrintStream
#50 = NameAndType #63:#64 // println:(Z)V
#51 = Utf8 com/example/Test
#52 = Utf8 java/lang/Object
#53 = Utf8 java/lang/String
#54 = Utf8 java/io/PrintStream
#55 = Utf8 append
#56 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#57 = Utf8 toString
#58 = Utf8 ()Ljava/lang/String;
#59 = Utf8 intern
#60 = Utf8 java/lang/System
#61 = Utf8 out
#62 = Utf8 Ljava/io/PrintStream;
#63 = Utf8 println
#64 = Utf8 (Z)V
{
public com.example.Test();
descriptor: ()V
flags: 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 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/Test;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=7, 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/String;
27: astore 4
29: ldc #4 // String ab
31: astore 5
33: aload 4
35: invokevirtual #9 // Method java/lang/String.intern:()Ljava/lang/String;
38: astore 6
40: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
43: aload_3
44: aload 4
46: if_acmpne 53
49: iconst_1
50: goto 54
53: iconst_0
54: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
57: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
60: aload_3
61: aload 5
63: if_acmpne 70
66: iconst_1
67: goto 71
70: iconst_0
71: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
74: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
77: aload_3
78: aload 6
80: if_acmpne 87
83: iconst_1
84: goto 88
87: iconst_0
88: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V
91: return
LineNumberTable:
line 6: 0
line 8: 3
line 10: 6
line 12: 9
line 14: 29
line 16: 33
line 18: 40
line 20: 57
line 22: 74
line 23: 91
LocalVariableTable:
Start Length Slot Name Signature
0 92 0 args [Ljava/lang/String;
3 89 1 s1 Ljava/lang/String;
6 86 2 s2 Ljava/lang/String;
9 83 3 s3 Ljava/lang/String;
29 63 4 s4 Ljava/lang/String;
33 59 5 s5 Ljava/lang/String;
40 52 6 s6 Ljava/lang/String;
StackMapTable: number_of_entries = 6
frame_type = 255 /* full_frame */
offset_delta = 53
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/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, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
frame_type = 79 /* same_locals_1_stack_item */
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, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
frame_type = 79 /* same_locals_1_stack_item */
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, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
}
SourceFile: "Test.java"
Process finished with exit code 0
s3=s4輸出false
我們看如圖反編譯代碼紅圈圈出部分:
從反編譯代碼中我們可以看出,String s4 = s1 + s2; 其實就是new StringBuilder().append("a“).append("b").toString,查看StringBuilder源碼的toString方法會發現,調用了new String(),那麼就會在堆中重新開闢一個內存空間,與之前的s3字符串常量池沒什麼關係,也就是指向地址都不一樣了,所以會輸出false
s3=s5輸出true
看如圖反編譯代碼紅圈圈出部分:
可以看出s3和s5都是對#4常量池的引用,爲true的原因是jvm存在編譯期優化的機制,在編譯期(javac *.java時)會將可以拼接的字符串常量幫你自動拼接了,由於字符串常量池中已經存在了,因此會讓s3指向一個與s5相同的那塊地址,因此爲true。
s3=s6輸出true
簡單介紹intern方法,可以使用intern方法,主動將串池中還沒有的字符串對象放入字符串常量池
通過intern方法主動將s4的字符串放入了字符串常量池,將這個字符串對象嘗試放入串池,如果有則不會放入,如果沒有則放入串池,會把串池中的對象返回,在放入s4的時候恰好存在,因此返回該存在的引用,所以s3 = s6爲true
考慮假如運行環境爲jdk1.6,s3==s6?
1.6將這個字符串對象嘗試放入串池,如果有則不會放入,如果沒有會把此對象複製一份,會把串池中的對象返回。
若常量池中不存在等值的字符串,JVM就會在常量池中創建一個等值的字符串,然後返回該字符串的引用
所以s3和s6的指向地址並不相同,所以會返回false