java 字符串內(nèi)存分配的分析與總結(jié)(推薦)
經(jīng)常在網(wǎng)上各大版塊都能看到對于java字符串運行時內(nèi)存分配的探討,形如:String a = "123",String b = new String("123"),這兩種形式的字符串是存放在什么地方的呢,其實這兩種形式的字符串字面值"123"本身在運行時既不是存放在棧上,也不是存放在堆上,他們是存放在方法區(qū)中的某個常量區(qū),并且對于相同的字符串字面值在內(nèi)存中只保留一份。下面我們將以實例來分析。
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);//程序運行結(jié)果為true
//part 2
String s3 = new String("i love china");
String s4 = new String("i love china");
System.out.println("result:" + s3 == s4);//程序運行結(jié)果為false
}
}
我們知道java中==運算符比較的是變量的值,對于引用類型對應(yīng)的變量的值存放的是引用對象的地址,在這里String是引用類型,這里面的四個變量的值存放的其實是指向字符串的地址。對于part2的執(zhí)行結(jié)果是顯然的,因為new操作符會使jvm在運行時在堆中創(chuàng)建新的對象,兩個不同的對象的地址是不同的。但是由part1的執(zhí)行結(jié)果,可以看出s1和s2是指向的同一個地址,那么由變量s1,s2指向的字符串是存放在什么地方的呢,jvm又是對字符串如何處理的呢。同樣的對于變量s3,s4所指向的堆中的不同的字符串對象,他們會分別在自己的對象空間中保存一份"i love china"字符串嗎,為了了解jvm是如何處理字符串,首先我們看java編譯器生成的字節(jié)碼指令。通過字節(jié)碼指令我們來分析jvm將會執(zhí)行哪些操作。
2.以下為程序生成的部分字節(jié)碼信息。紅色標注的是我們需要關(guān)注的部分。
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運行時解釋執(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 //彈出棧頂兩個對象引用進行比較其是否相等,不等,轉(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都是指向常量池中的同一個字符串常量。在運行時jvm會將字節(jié)碼常量池中的字符串常量存放在方法區(qū)中的通常稱之為常量池的位置,并且字符串是以字符數(shù)組的形式通過索引來訪問的。jvm在運行時將s1與s2指向的字符串相對引用地址指向字符串實際的內(nèi)存地址。
2). 對于String s3 = new String("i love china"),String s4 = new String("i love china"),由字節(jié)碼可以看出其是調(diào)用了new指令,jvm會在運行時創(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")時,在對象內(nèi)部是做了字符串的拷貝還是直接指向該字符串對應(yīng)的常量池的地址的引用。
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[],通過構(gòu)造方法我們可知,對象在初始化時并沒有做拷貝操作,只是將傳遞進來的字符串對象的地址引用賦給了實例變量value。由此我們可以初步的得出結(jié)論:即使使用new String("abc")創(chuàng)建了一個字符串對象時,在內(nèi)存堆中為該對象分配了空間,但是在堆上并沒有存儲"abc"本身的任何信息,只是初始化了其內(nèi)部的實例變量到"abc"字符串的引用。其實這樣做也是為了節(jié)省內(nèi)存的存儲空間,以及提高程序的性能。
4.下面我們來看看String對象部分字節(jié)碼信息:
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處的實例變量,并將其存放到棧上。
9: putfield #2 // Field value:[C // 彈出棧頂?shù)淖址眉皩ο笞陨淼囊貌⒆址囊觅x值給本對象自身的實例變量。
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)造新對象時執(zhí)行的是字符串引用的賦值,而不是字符串的拷貝。以上是從源碼及字節(jié)碼的角度來對字符串的內(nèi)存分配進行的分析與總結(jié)。
以上這篇java 字符串內(nèi)存分配的分析與總結(jié)(推薦)就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
從零開始搭建springboot+springcloud+mybatis本地項目全過程(圖解)
這篇文章主要介紹了從零開始搭建springboot+springcloud+mybatis本地項目全過程(圖解),本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
華為鴻蒙系統(tǒng)應(yīng)用開發(fā)工具 DevEco Studio的安裝和使用圖文教程
HUAWEI DevEco Studio 是華為消費者業(yè)務(wù)為開發(fā)者提供的集成開發(fā)環(huán)境(IDE),旨在幫助開發(fā)者快捷、方便、高效地使用華為EMUI開放能力。這篇文章主要介紹了華為鴻蒙系統(tǒng)應(yīng)用開發(fā)工具 DevEco Studio的安裝和使用圖文教程,需要的朋友可以參考下2021-04-04
Sentinel源碼解析入口類和SlotChain構(gòu)建過程詳解
這篇文章主要為大家介紹了Sentinel源碼解析入口類和SlotChain構(gòu)建過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
我總結(jié)的幾種@Transactional失效原因說明
這篇文章主要是我總結(jié)的幾種@Transactional失效原因說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
SpringBoot 任務(wù)調(diào)度動態(tài)設(shè)置方式(不用重啟服務(wù))
這篇文章主要介紹了SpringBoot 任務(wù)調(diào)度 動態(tài)設(shè)置方式(不用重啟服務(wù)),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Java中字符串String的+和+=及循環(huán)操作String原理詳解
Java編譯器在編譯時對String的+和+=操作會創(chuàng)建StringBuilder對象來進行字符串的拼接,下面這篇文章主要給大家介紹了關(guān)于Java中字符串String的+和+=及循環(huán)操作String原理的相關(guān)資料,需要的朋友可以參考下2023-01-01

