棧幀(Stack Frame) 是用於虛擬機執行時方法調用和方法執行時的數據結構,它是虛擬棧數據區的組成元素。每一個方法從調用到方法返回都對應着一個棧幀入棧出棧的過程。
每一個棧幀在編譯程序代碼的時候所需要多大的局部變量表,多深的操作數棧都已經決定了,並且寫入到方發表的 Code 屬性之中,一次一個棧幀需要多少內存,不會受到程序運行期變量數據的影響,僅僅取決於具體的虛擬機實現。
典型的棧幀主要由 局部變量表(Local Stack Frame)、操作數棧(Operand Stack)、動態鏈接(Dynamic Linking)、返回地址(Return Address)組成,如下圖所示:
public static void main(String[] args) { String s1 = "Hello"; String s2 = "Hello";
//s3雖然是動態拼接出來的字符串,但是所有參與拼接的部分都是已知的字面量,在編譯期間,這種拼接會被優化,編譯器直接幫你拼好, String s3 = "Hel" + "lo"; //Hel被包裝為對象,lo被包裝為對象, //然后調用 StringBuilder.append方法, //然后調用String.toStringui方法, //存入變量s4 String s4 = "Hel" + new String("lo"); //調用new 生成新對象 String s5 = new String("Hello"); String s6 = s5.intern(); String s7 = "H"; String s8 = "ello"; String s9 = s7 + s8; System.out.println(s1 == s2); // true System.out.println(s1 == s3); // true System.out.println(s1 == s4); // false System.out.println(s1 == s9); // false System.out.println(s4 == s5); // false System.out.println(s1 == s6); // true }
其對應指令集如下:
0: ldc #5 // String Hello 2: astore_1 3: ldc #5 // String Hello 5: astore_2 6: ldc #5 // String Hello 8: astore_3 9: new #6 // class java/lang/StringBuilder 12: dup 13: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 16: ldc #8 // String Hel 18: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: new #10 // class java/lang/String 24: dup 25: ldc #11 // String lo 27: invokespecial #12 // Method java/lang/String."<init>":(Ljava/lang/String;)V 30: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 33: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 36: astore 4 38: new #10 // class java/lang/String 41: dup 42: ldc #5 // String Hello 44: invokespecial #12 // Method java/lang/String."<init>":(Ljava/lang/String;)V 47: astore 5 49: aload 5 51: invokevirtual #14 // Method java/lang/String.intern:()Ljava/lang/String; 54: astore 6 56: ldc #15 // String H 58: astore 7 60: ldc #16 // String ello 62: astore 8 64: new #6 // class java/lang/StringBuilder 67: dup 68: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 71: aload 7 73: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 76: aload 8 78: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 81: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 84: astore 9
常量池:
Java中的常量池,實際上分為兩種形態:靜態常量池和運行時常量池。
所謂靜態常量池,即*.class文件中的常量池,class文件中的常量池不僅僅包含字符串(數字)字面量,還包含類、方法的信息,占用class文件絕大部分空間。
這種常量池主要用於存放兩大類常量:字面量(Literal)和符號引用量(Symbolic References),
字面量相當於Java語言層面常量的概念,如文本字符串,聲明為final的常量值等,
符號引用則屬於編譯原理方面的概念,包括了如下三種類型的常量:
- 類和接口的全限定名
- 字段名稱和描述符
- 方法名稱和描述符
而運行時常量池,則是jvm虛擬機在完成類裝載操作后,將class文件中的常量池載入到內存中,並保存在方法區中,我們常說的常量池,就是指方法區中的運行時常量池。
運行時常量池相對於CLass文件常量池的另外一個重要特征是
具備動態性,Java語言並不要求常量一定只有編譯期才能產生,也就是並非預置入CLass文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的就是
String類的intern()方法。
String的intern()方法會查找在常量池中是否存在一份equal相等的字符串,如果有則返回該字符串的引用,如果沒有則添加自己的字符串進入常量池。
常量池的好處
常量池是為了避免頻繁的創建和銷毀對象而影響系統性能,其實現了對象的共享。
例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。
(1)節省內存空間:常量池中所有相同的字符串常量被合並,只占用一個空間。
(2)節省運行時間:比較字符串時,==比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就可以判斷實際值是否相等。
常量池是為了避免頻繁的創建和銷毀對象而影響系統性能,其實現了對象的共享。
例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。
(1)節省內存空間:常量池中所有相同的字符串常量被合並,只占用一個空間。
(2)節省運行時間:比較字符串時,==比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就可以判斷實際值是否相等。