這裏都是針對jdk1.8的hotspot虛擬機講解
public class Demo1_1{
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1+s2;
String s5 = "a" + "b";
}
}
通過在命令窗口反編譯class文件得到如下信息: 認識二進制字節碼文件內容(三)
D:\workspace\day01easyjdbc01\target\classes\com\demo\jvm>javap -v Demo1_1.class
Classfile /D:/workspace/day01easyjdbc01/target/classes/com/demo/jvm/Demo1_1.class
Last modified 2020-3-29; size 706 bytes
MD5 checksum 0ba87b6925bb2d842174a3e3f4a1c3df
Compiled from "Demo1_1.java"
public class com.demo.jvm.Demo1_1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #10.#30 // java/lang/Object."<init>":()V
#2 = String #31 // a
#3 = String #32 // b
#4 = String #33 // ab
#5 = Class #34 // java/lang/StringBuilder
#6 = Methodref #5.#30 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#35 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Class #37 // com/demo/jvm/Demo1_1
#10 = Class #38 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Lcom/demo/jvm/Demo1_1;
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 args
#21 = Utf8 [Ljava/lang/String;
#22 = Utf8 s1
#23 = Utf8 Ljava/lang/String;
#24 = Utf8 s2
#25 = Utf8 s3
#26 = Utf8 s4
#27 = Utf8 s5
#28 = Utf8 SourceFile
#29 = Utf8 Demo1_1.java
#30 = NameAndType #11:#12 // "<init>":()V
#31 = Utf8 a
#32 = Utf8 b
#33 = Utf8 ab
#34 = Utf8 java/lang/StringBuilder
#35 = NameAndType #39:#40 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#36 = NameAndType #41:#42 // toString:()Ljava/lang/String;
#37 = Utf8 com/demo/jvm/Demo1_1
#38 = Utf8 java/lang/Object
#39 = Utf8 append
#40 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#41 = Utf8 toString
#42 = Utf8 ()Ljava/lang/String;
{
public com.demo.jvm.Demo1_1();
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/demo/jvm/Demo1_1;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=6, 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: return
LineNumberTable:
line 5: 0
line 6: 3
line 7: 6
line 8: 9
line 9: 29
line 10: 33
LocalVariableTable:
Start Length Slot Name Signature
0 34 0 args [Ljava/lang/String;
3 31 1 s1 Ljava/lang/String;
6 28 2 s2 Ljava/lang/String;
9 25 3 s3 Ljava/lang/String;
29 5 4 s4 Ljava/lang/String;
33 1 5 s5 Ljava/lang/String;
}
SourceFile: "Demo1_1.java"
1. String s1 = "a" 代碼說明
根據虛擬機指令0,解釋器執行了運行時常量池裏面的#2,這個時候常量池從空值狀態變成了存放了一個符號a(這裏只是一個符號),然後根據#31 加載字符串對象"a",常量"a" 壓入操作棧
接着執行虛擬機指令2,執行astore_1把剛纔的字符串對象"a"保存到LocalVariableTable局部變量表中Slot位置1,變量名是s1
最後就是jvm棧中變量名爲s1的指向堆內存中的常量池裏面的"a"
同理 String s2 = "b"
最後把字符串對象"b"保存到局部變量表的中位置2,變量名爲s2.
2.接下來在看看String s3 = "ab" 代碼說明:
這裏執行虛擬機指令9,調用new關鍵字 最後在堆空間創建了一個StringBuilder對象。
執行指令13,調用了init的無參構造函數
執行指令16,調用aload_1,從局部變量表中拿到s1,
執行指令17,調用StringBuilder.append方法,把s1傳入方法中,
同理執行20.21,把s2傳入方法中
執行24,調用StrinbBuilder.toString方法
執行27,執行 astore 4 ,把toString 轉換後的結果存入局部變量表中的4號位置。
通過查看StringBuilder源代碼中toString方法,也可以看出是new了一個新的字符串對象。
3.接着看String s5 = "a" + "b"說明
執指令行29,找到的常量池中#4,也就是符號ab,也就是說常量池中如果已經存在要找的字符串,不需要在存放,直接拿來使用即可
執行指令31,存放到局部變量表中位置5
執行33,方法結束
小知識點:可以通過調用intern()方法,嘗試講字符串放入常量池中,有則放入,並返回串池中的對象
1.6版本中,如果串池中沒有當前放入的常量,則是copy一份放入串池的
String a1 = "a" + "b" ;
String a2 = a.intern();
總結:
- 字符串對象是不可變(包裝類也如此)
- 第一次使用常量池中的符號才變成對象
- 字符串變量拼接 原理是StringBuilder.append(),編譯期間無法確定字符串變量引用的值,運行期才能確定
- 字符串常量是編譯期間就能確定對象