經(jīng)常在網(wǎng)上各大版塊都能看到對于java字符串運(yùn)行時(shí)內(nèi)存分配的探討,形如:String a = "123",String b = new String("123"),這兩種形式的字符串是存放在什么地方的呢,其實(shí)這兩種形式的字符串字面值"123"本身在運(yùn)行時(shí)既不是存放在棧上,也不是存放在堆上,他們是存放在方法區(qū)中的某個常量區(qū),并且對于相同的字符串字面值在內(nèi)存中只保留一份。下面我們將以實(shí)例來分析。
1.==運(yùn)算符作用在兩個字符串引用比較的兩個案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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); //程序運(yùn)行結(jié)果為true //part 2 String s3 = new String( "i love china" ); String s4 = new String( "i love china" ); System.out.println( "result:" + s3 == s4); //程序運(yùn)行結(jié)果為false } } |
我們知道java中==運(yùn)算符比較的是變量的值,對于引用類型對應(yīng)的變量的值存放的是引用對象的地址,在這里String是引用類型,這里面的四個變量的值存放的其實(shí)是指向字符串的地址。對于part2的執(zhí)行結(jié)果是顯然的,因?yàn)閚ew操作符會使jvm在運(yùn)行時(shí)在堆中創(chuàng)建新的對象,兩個不同的對象的地址是不同的。但是由part1的執(zhí)行結(jié)果,可以看出s1和s2是指向的同一個地址,那么由變量s1,s2指向的字符串是存放在什么地方的呢,jvm又是對字符串如何處理的呢。同樣的對于變量s3,s4所指向的堆中的不同的字符串對象,他們會分別在自己的對象空間中保存一份"i love china"字符串嗎,為了了解jvm是如何處理字符串,首先我們看java編譯器生成的字節(jié)碼指令。通過字節(jié)碼指令我們來分析jvm將會執(zhí)行哪些操作。
2.以下為程序生成的部分字節(jié)碼信息。紅色標(biāo)注的是我們需要關(guān)注的部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
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 ........... //對應(yīng)的方法的字節(jié)碼指令,由jvm運(yùn)行時(shí)解釋執(zhí)行。 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對應(yīng)于程序中的String s1 = "i love china"語句 2 : astore_1 //將棧頂?shù)膶ο笠觅x值給局部變量1. 3 : ldc # 16 // String i love china,同0處的指令,指向的是同一個符號引用處的常量。該指令與底下的指令5對應(yīng)于程序中的 String s2 = "i love china"語句。 5 : astore_2 //將棧頂?shù)膶ο笠觅x值給局部變量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 //彈出棧頂兩個對象引用進(jìn)行比較其是否相等,不等,轉(zhuǎn)到指令27處,執(zhí)行,相等執(zhí)行下一條指令 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,創(chuàng)建一個對象,該對象位于常量池#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//創(chuàng)建一個對象,并將對象引用push到棧頂 50 : dup 51 : ldc # 16 // String i love china, 將字符串的符號引用push到棧頂。 53 : invokespecial # 46 // Method java/lang/String."<init>":(Ljava/lang/String;)V,根據(jù)棧頂?shù)膶?yīng)的對象引用及字符串引用調(diào)用對象的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 |
字節(jié)碼中紅色的部分是與我們討論相關(guān)的。通過生成的字節(jié)碼,我們可以對示例程序得出如下結(jié)論。
1). java編譯器在將程序編譯成字節(jié)碼的過程中,對遇到的字符串常量"i love china"首先判斷其是否在字節(jié)碼常量池中存在,不存在創(chuàng)建一份,存在的話則不創(chuàng)建,也就是相等的字符串,只保留一份,通過符號引用可以找到它,這樣使得程序中的字符串變量s1和s2都是指向常量池中的同一個字符串常量。在運(yùn)行時(shí)jvm會將字節(jié)碼常量池中的字符串常量存放在方法區(qū)中的通常稱之為常量池的位置,并且字符串是以字符數(shù)組的形式通過索引來訪問的。jvm在運(yùn)行時(shí)將s1與s2指向的字符串相對引用地址指向字符串實(shí)際的內(nèi)存地址。
2). 對于String s3 = new String("i love china"),String s4 = new String("i love china"),由字節(jié)碼可以看出其是調(diào)用了new指令,jvm會在運(yùn)行時(shí)創(chuàng)建兩個不同的對象,s3與s4指向的是不同的對象地址。所以s3==s4比較的結(jié)果為false。
其次,對于s3與s4對象的初始化,從字節(jié)碼看出是調(diào)用對象的init方法并且傳遞的是常量池中”i love china”的引用,那么創(chuàng)建String對象及初始化究竟干了什么,我們可以查看通過查看String的源碼及String對象生成的字節(jié)碼,以便更好的了解對于new String("i love china")時(shí),在對象內(nèi)部是做了字符串的拷貝還是直接指向該字符串對應(yīng)的常量池的地址的引用。
3.String對象的部分源碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<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類里有個實(shí)例變量 char value[],通過構(gòu)造方法我們可知,對象在初始化時(shí)并沒有做拷貝操作,只是將傳遞進(jìn)來的字符串對象的地址引用賦給了實(shí)例變量value。由此我們可以初步的得出結(jié)論:即使使用new String("abc")創(chuàng)建了一個字符串對象時(shí),在內(nèi)存堆中為該對象分配了空間,但是在堆上并沒有存儲"abc"本身的任何信息,只是初始化了其內(nèi)部的實(shí)例變量到"abc"字符串的引用。其實(shí)這樣做也是為了節(jié)省內(nèi)存的存儲空間,以及提高程序的性能。
4.下面我們來看看String對象部分字節(jié)碼信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
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 彈出棧頂對象引用調(diào)用該對象的#1處的初始化方法。 4 : aload_0 //將自身對象引用push到棧頂。 5 : aload_1 //傳遞的字符串引用push到棧頂。 6 : getfield # 2 // Field value:[C // 彈出棧頂?shù)淖址貌⑵滟x值給#2處的實(shí)例變量,并將其存放到棧上。 9 : putfield # 2 // Field value:[C // 彈出棧頂?shù)淖址眉皩ο笞陨淼囊貌⒆址囊觅x值給本對象自身的實(shí)例變量。 12 : aload_0 13 : aload_1 14 : getfield # 3 // Field hash:I 17 : putfield # 3 // Field hash:I 20 : return |
從字節(jié)碼的角度我們可以得出結(jié)論,new String("abc")在構(gòu)造新對象時(shí)執(zhí)行的是字符串引用的賦值,而不是字符串的拷貝。以上是從源碼及字節(jié)碼的角度來對字符串的內(nèi)存分配進(jìn)行的分析與總結(jié)。
以上這篇java 字符串內(nèi)存分配的分析與總結(jié)(推薦)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持服務(wù)器之家。