java 字符串內存分派的剖析與總結(推舉)。本站提示廣大學習愛好者:(java 字符串內存分派的剖析與總結(推舉))文章只能為提供參考,不一定能成為您想要的結果。以下是java 字符串內存分派的剖析與總結(推舉)正文
常常在網上各年夜版塊都能看到關於java字符串運轉時內存分派的商量,形如:String a = "123",String b = new String("123"),這兩種情勢的字符串是寄存在甚麼處所的呢,其實這兩種情勢的字符串字面值"123"自己在運轉時既不是寄存在棧上,也不是寄存在堆上,他們是寄存在辦法區中的某個常量區,而且關於雷同的字符串字面值在內存中只保存一份。上面我們將以實例來剖析。
1.==運算符感化在兩個字符串援用比擬的兩個案例:
public class StringTest { public static void main(String[] args) { //part 1 String s1 = "i love china"; String s2 = "i love china"; System.out.println("result:" + s1 == s2);//法式運轉成果為true //part 2 String s3 = new String("i love china"); String s4 = new String("i love china"); System.out.println("result:" + s3 == s4);//法式運轉成果為false } }
我們曉得java中==運算符比擬的是變量的值,關於援用類型對應的變量的值寄存的是援用對象的地址,在這裡String是援用類型,這外面的四個變量的值寄存的實際上是指向字符串的地址。關於part2的履行成果是明顯的,由於new操作符會使jvm在運轉時在堆中創立新的對象,兩個分歧的對象的地址是分歧的。然則由part1的履行成果,可以看出s1和s2是指向的統一個地址,那末由變量s1,s2指向的字符串是寄存在甚麼處所的呢,jvm又是對字符串若何處置的呢。異樣的關於變量s3,s4所指向的堆中的分歧的字符串對象,他們會分離在本身的對象空間中保留一份"i love china"字符串嗎,為了懂得jvm是若何處置字符串,起首我們看java編譯器生成的字節碼指令。經由過程字節碼指令我們來剖析jvm將會履行哪些操作。
2.以下為法式生成的部門字節碼信息。白色標注的是我們須要存眷的部門。
Constant pool: #1 = Class #2 // StringTest #2 = Utf8 StringTest #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Methodref #3.#9 // java/lang/Object."<init>":()V #9 = NameAndType #5:#6 // "<init>":()V #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 LStringTest; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = String #17 // i love china 字符串地址的援用 #17 = Utf8 i love china #18 = Fieldref #19.#21 // java/lang/System.out:Ljava/io/PrintStream; #19 = Class #20 // java/lang/System #20 = Utf8 java/lang/System #21 = NameAndType #22:#23 // out:Ljava/io/PrintStream; #22 = Utf8 out #23 = Utf8 Ljava/io/PrintStream; #24 = Class #25 // java/lang/StringBuilder #25 = Utf8 java/lang/StringBuilder #26 = String #27 // result: #27 = Utf8 result: #28 = Methodref #24.#29 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V #29 = NameAndType #5:#30 // "<init>":(Ljava/lang/String;)V #30 = Utf8 (Ljava/lang/String;)V #31 = Methodref #24.#32 // java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; #32 = NameAndType #33:#34 // append:(Z)Ljava/lang/StringBuilder; #33 = Utf8 append #34 = Utf8 (Z)Ljava/lang/StringBuilder; #35 = Methodref #24.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String; #36 = NameAndType #37:#38 // toString:()Ljava/lang/String; #37 = Utf8 toString #38 = Utf8 ()Ljava/lang/String; #39 = Methodref #40.#42 // java/io/PrintStream.println:(Ljava/lang/String;)V #40 = Class #41 // java/io/PrintStream #41 = Utf8 java/io/PrintStream #42 = NameAndType #43:#30 // println:(Ljava/lang/String;)V #43 = Utf8 println #44 = Class #45 // java/lang/String #45 = Utf8 java/lang/String #46 = Methodref #44.#29 // java/lang/String."<init>":(Ljava/lang/String;)V #47 = Utf8 args #48 = Utf8 [Ljava/lang/String; #49 = Utf8 s1 #50 = Utf8 Ljava/lang/String; #51 = Utf8 s2 #52 = Utf8 s3 #53 = Utf8 s4 #54 = Utf8 StackMapTable #55 = Class #48 // "[Ljava/lang/String;" #56 = Utf8 SourceFile #57 = Utf8 StringTest.java ........... //對應的辦法的字節碼指令,由jvm運轉時說明履行。 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=5, args_size=1 0: ldc #16 // String i love china,該指令是將常量池的#16處符號援用,在這裡為字符串“ilove china”符號援用push到棧頂。該指令與底下的指令2對應於法式中的String s1 = "i love china"語句 2: astore_1 //將棧頂的對象援用賦值給部分變量1. 3: ldc #16 // String i love china,同0處的指令,指向的是統一個符號援用處的常量。該指令與底下的指令5對應於法式中的 String s2 = "i love china"語句。 5: astore_2 //將棧頂的對象援用賦值給部分變量2. 6: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream; 9: new #24 // class java/lang/StringBuilder 12: dup 13: ldc #26 // String result: 15: invokespecial #28 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 18: aload_1 19: aload_2 20: if_acmpne 27 //彈出棧頂兩個對象援用停止比擬其能否相等,不等,轉到指令27處,履行,相等履行下一條指令 23: iconst_1 24: goto 28 27: iconst_0 28: invokevirtual #31 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 31: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 34: invokevirtual #39 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 37: new #44 // class java/lang/String,創立一個對象,該對象位於常量池#44援用處,這裡為String對象,並將對象援用push到棧頂。 40: dup //拷貝棧頂一份對象援用push到棧頂。 41: ldc #16 // String i love china,同0,3處指令。 43: invokespecial #46 // Method java/lang/String."<init>":(Ljava/lang/String;)V 46: astore_3 47: new #44 // class java/lang/String//創立一個對象,並將對象援用push到棧頂 50: dup 51: ldc #16 // String i love china, 將字符串的符號援用push到棧頂。 53: invokespecial #46 // Method java/lang/String."<init>":(Ljava/lang/String;)V,依據棧頂的對應的對象援用及字符串援用挪用對象的init初始化辦法,對字符串對象初始化 56: astore 4 //將棧頂對象援用賦值給變量4. 58: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream; 61: new #24 // class java/lang/StringBuilder 64: dup 65: ldc #26 // String result: 67: invokespecial #28 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 70: aload_3 71: aload 4 73: if_acmpne 80 76: iconst_1 77: goto 81 80: iconst_0 81: invokevirtual #31 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 84: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 87: invokevirtual #39 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 90: return ......... LineNumberTable: line 7: 0 line 8: 3 line 9: 6 line 11: 37 line 12: 47 line 13: 58 line 14: 90 LocalVariableTable: Start Length Slot Name Signature 91 0 args [Ljava/lang/String;//部分變量0 88 1 s1 Ljava/lang/String; //部分變量1 85 2 s2 Ljava/lang/String;//部分變量2 44 3 s3 Ljava/lang/String;//部分變量3 33 4 s4 Ljava/lang/String;//部分變量4
字節碼中白色的部門是與我們評論辯論相干的。經由過程生成的字節碼,我們可以對示例法式得出以下結論。
1). java編譯器在將法式編譯成字節碼的進程中,對碰到的字符串常量"i love china"起首斷定其能否在字節碼常量池中存在,不存在創立一份,存在的話則不創立,也就是相等的字符串,只保存一份,經由過程符號援用可以找到它,如許使得法式中的字符串變量s1和s2都是指向常量池中的統一個字符串常量。在運轉時jvm會將字節碼常量池中的字符串常量寄存在辦法區中的平日稱之為常量池的地位,而且字符串是以字符數組的情勢經由過程索引來拜訪的。jvm在運轉時將s1與s2指向的字符串絕對援用地址指向字符串現實的內存地址。
2). 關於String s3 = new String("i love china"),String s4 = new String("i love china"),由字節碼可以看出其是挪用了new指令,jvm會在運轉時創立兩個分歧的對象,s3與s4指向的是分歧的對象地址。所以s3==s4比擬的成果為false。
其次,關於s3與s4對象的初始化,從字節碼看出是挪用對象的init辦法而且傳遞的是常量池中”i love china”的援用,那末創立String對象及初始化畢竟干了甚麼,我們可以檢查經由過程檢查String的源碼及String對象生成的字節碼,以便更好的懂得關於new String("i love china")時,在對象外部是做了字符串的拷貝照樣直接指向該字符串對應的常量池的地址的援用。
3.String對象的部門源碼:
<SPAN >public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 public String() { this.value = new char[0]; }</SPAN> <SPAN > public String(String original) { this.value = original.value; this.hash = original.hash; } </SPAN>
從源碼中我們看到String類裡有個實例變量 char value[],經由過程結構辦法我們可知,對象在初始化時並沒有做拷貝操作,只是將傳遞出去的字符串對象的地址援用賦給了實例變量value。由此我們可以初步的得出結論:即便應用new String("abc")創立了一個字符串對象時,在內存堆中為該對象分派了空間,然則在堆上並沒有存儲"abc"自己的任何信息,只是初始化了其外部的實例變量到"abc"字符串的援用。其實如許做也是為了節儉內存的存儲空間,和進步法式的機能。
4.上面我們來看看String對象部門字節碼信息:
public java.lang.String(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_0 6: newarray char 8: putfield #2 // Field value:[C 11: return LineNumberTable: line 137: 0 line 138: 4 line 139: 11 public java.lang.String(java.lang.String); descriptor: (Ljava/lang/String;)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 //將部分變量0push到棧頂,本身對象的援用。 1: invokespecial #1 // Method java/lang/Object."<init>":()V 彈出棧頂對象援用挪用該對象的#1處的初始化辦法。 4: aload_0 //將本身對象援用push到棧頂。 5: aload_1 //傳遞的字符串援用push到棧頂。 6: getfield #2 // Field value:[C // 彈出棧頂的字符串援用並將其賦值給#2處的實例變量,並將其寄存到棧上。 9: putfield #2 // Field value:[C // 彈出棧頂的字符串援用及對象本身的援用並將字符串的援用賦值給本對象本身的實例變量。 12: aload_0 13: aload_1 14: getfield #3 // Field hash:I 17: putfield #3 // Field hash:I 20: return
從字節碼的角度我們可以得出結論,new String("abc")在結構新對象時履行的是字符串援用的賦值,而不是字符串的拷貝。以上是從源碼及字節碼的角度來對字符串的內存分派停止的剖析與總結。
以上這篇java 字符串內存分派的剖析與總結(推舉)就是小編分享給年夜家的全體內容了,願望能給年夜家一個參考,也願望年夜家多多支撐。