国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

java 字符串內存分配的分析與總結(推薦)

2019-11-26 13:58:22
字體:
來源:轉載
供稿:網友

經常在網上各大版塊都能看到對于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: 0line 8: 3line 9: 6line 11: 37line 12: 47line 13: 58line 14: 90LocalVariableTable:Start Length Slot Name Signature91 0 args [Ljava/lang/String;//局部變量088 1 s1 Ljava/lang/String; //局部變量185 2 s2 Ljava/lang/String;//局部變量244 3 s3 Ljava/lang/String;//局部變量333 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 style="FONT-SIZE: 14pt">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 style="BACKGROUND-COLOR: #ffffff; FONT-SIZE: 18pt"> 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 字符串內存分配的分析與總結(推薦)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 东兴市| 抚顺县| 安泽县| 平凉市| 三江| 安福县| 醴陵市| 台湾省| 云梦县| 金昌市| 达日县| 图们市| 收藏| 景宁| 台湾省| 六安市| 耒阳市| 襄垣县| 尼木县| 资溪县| 龙里县| 甘南县| 唐山市| 安多县| SHOW| 桓仁| 吉安市| 瑞丽市| 白水县| 农安县| 奈曼旗| 出国| 灵宝市| 枝江市| 柏乡县| 沂水县| 库尔勒市| 岚皋县| 苗栗市| 浦江县| 呼玛县|