淺談Java String內(nèi)幕

簡書 占小狼
轉(zhuǎn)載請注明原創(chuàng)出處彬碱,謝謝!

前言

String字符串在Java應用中使用非常頻繁婴程,只有理解了它在虛擬機中的實現(xiàn)機制晋辆,才能寫出健壯的應用渠脉,本文使用的JDK版本為1.8.0_3。

常量池

Java代碼被編譯成class文件時瓶佳,會生成一個常量池(Constant pool)的數(shù)據(jù)結(jié)構(gòu)芋膘,用以保存字面常量和符號引用(類名、方法名霸饲、接口名和字段名等)为朋。

package com.ctrip.ttd.whywhy;
public class Test {  
    public static void main(String[] args) {  
        String test = "test";  
    }  
}  

很簡單的一段代碼,通過命令 javap -verbose 查看class文件中 Constant pool 實現(xiàn):

Constant pool:
   #1 = Methodref          #4.#13         // java/lang/Object."<init>":()V
   #2 = String             #14            // test
   #3 = Class              #15            // com/ctrip/ttd/whywhy/test
   #4 = Class              #16            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               main
  #10 = Utf8               ([Ljava/lang/String;)V
  #11 = Utf8               SourceFile
  #12 = Utf8               test.java
  #13 = NameAndType        #5:#6          // "<init>":()V
  #14 = Utf8               test
  #15 = Utf8               com/ctrip/ttd/whywhy/test
  #16 = Utf8               java/lang/Object

通過反編譯出來的字節(jié)碼可以看出字符串 "test" 在常量池中的定義方式:

#2 = String             #14            // test
#14 = Utf8              test

在main方法字節(jié)碼指令中厚脉,0 ~ 2行對應代碼 String test = "test"; 由兩部分組成:ldc #2 和 astore_1习寸。

 // main方法字節(jié)碼指令
 public static void main(java.lang.String[]);
   Code:
      0: ldc           #2                  // String test
      2: astore_1
      3: return

1、Test類加載到虛擬機時傻工,"test"字符串在Constant pool中使用符號引用symbol表示融涣,當調(diào)用 ldc #2 指令時,如果Constant pool中索引 #2 的symbol還未解析精钮,則調(diào)用C++底層的 StringTable::intern 方法生成char數(shù)組,并將引用保存在StringTable和常量池中剃斧,當下次調(diào)用 ldc #2 時轨香,可以直接從Constant pool根據(jù)索引 #2獲取 "test" 字符串的引用,避免再次到StringTable中查找幼东。

2臂容、astore_1指令將"test"字符串的引用保存在局部變量表中。

常量池的內(nèi)存分配 在 JDK6根蟹、7脓杉、8中有不同的實現(xiàn):
1、JDK6及之前版本中简逮,常量池的內(nèi)存在永久代PermGen進行分配球散,所以常量池會受到PermGen內(nèi)存大小的限制。
2散庶、JDK7中蕉堰,常量池的內(nèi)存在Java堆上進行分配,意味著常量池不受固定大小的限制了悲龟。
3屋讶、JDK8中,虛擬機團隊移除了永久代PermGen须教。

字符串初始化

字符串可以通過兩種方式進行初始化:字面常量和String對象皿渗。

字面常量

public class StringTest {
    public static void main(String[] args) {
        String a = "java";
        String b = "java";
        String c = "ja" + "va";
    }
}

通過 "javap -c" 命令查看字節(jié)碼指令實現(xiàn):


其中l(wèi)dc指令將int、float和String類型的常量值從常量池中推送到棧頂,所以a和b都指向常量池的"java"字符串乐疆。通過指令實現(xiàn)可以發(fā)現(xiàn):變量a划乖、b和c都指向常量池的 "java" 字符串,表達式 "ja" + "va" 在編譯期間會把結(jié)果值"java"直接賦值給c诀拭。

String對象

public class StringTest {
    public static void main(String[] args) {
        String a = "java";
        String c = new String("java");
    }
}

這種情況下迁筛,a == c 成立么?字節(jié)碼實現(xiàn)如下:


其中3 ~ 9行指令對應代碼 String c = new String("java"); 實現(xiàn):
1耕挨、第3行new指令细卧,在Java堆上為String對象申請內(nèi)存;
2筒占、第7行l(wèi)dc指令贪庙,嘗試從常量池中獲取"java"字符串,如果常量池中不存在翰苫,則在常量池中新建"java"字符串止邮,并返回;
3奏窑、第9行invokespecial指令导披,調(diào)用構(gòu)造方法,初始化String對象埃唯。

其中String對象中使用char數(shù)組存儲字符串撩匕,變量a指向常量池的"java"字符串,變量c指向Java堆的String對象墨叛,且該對象的char數(shù)組指向常量池的"java"字符串止毕,所以很顯然 a != c,如下圖所示:


**通過 "字面量 + String對象" 進行賦值會發(fā)生什么漠趁? **

public class StringTest {
    public static void main(String[] args) {
        String a = "hello ";
        String b = "world";
        String c = a + b;
        String d = "hello world";
    }
}

這種情況下扁凛,c == d成立么?字節(jié)碼實現(xiàn)如下:


其中6 ~ 21行指令對應代碼 String c = a + b; 實現(xiàn):
1闯传、第6行new指令谨朝,在Java堆上為StringBuilder對象申請內(nèi)存;
2甥绿、第10行invokespecial指令叠必,調(diào)用構(gòu)造方法,初始化StringBuilder對象妹窖;
3纬朝、第14、18行invokespecial指令骄呼,調(diào)用append方法共苛,添加a和b字符串判没;
4、第21行invokespecial指令隅茎,調(diào)用toString方法澄峰,生成String對象。

通過指令實現(xiàn)可以發(fā)現(xiàn)辟犀,字符串變量的連接動作俏竞,在編譯階段會被轉(zhuǎn)化成StringBuilder的append操作,變量c最終指向Java堆上新建String對象堂竟,變量d指向常量池的"hello world"字符串魂毁,所以 c != d。

不過有種特殊情況出嘹,當final修飾的變量發(fā)生連接動作時席楚,虛擬機會進行優(yōu)化,將表達式結(jié)果直接賦值給目標變量:

public class StringTest {
    public static void main(String[] args) {
        final String a = "hello ";
        final String b = "world";
        String c = a + b;
        String d = "hello world";
    }
}

指令實現(xiàn)如下:

END税稼。
我是占小狼烦秩。
在魔都艱苦奮斗,白天是上班族郎仆,晚上是知識服務工作者只祠。
如果讀完覺得有收獲的話,記得關(guān)注和點贊哦扰肌。
非要打賞的話抛寝,我也是不會拒絕的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狡耻,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猴凹,更是在濱河造成了極大的恐慌夷狰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郊霎,死亡現(xiàn)場離奇詭異沼头,居然都是意外死亡,警方通過查閱死者的電腦和手機书劝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門进倍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人购对,你說我怎么就攤上這事猾昆。” “怎么了骡苞?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵垂蜗,是天一觀的道長楷扬。 經(jīng)常有香客問我,道長贴见,這世上最難降的妖魔是什么烘苹? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮片部,結(jié)果婚禮上镣衡,老公的妹妹穿的比我還像新娘。我一直安慰自己档悠,他們只是感情好廊鸥,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著站粟,像睡著了一般黍图。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上奴烙,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天助被,我揣著相機與錄音,去河邊找鬼切诀。 笑死揩环,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的幅虑。 我是一名探鬼主播丰滑,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼倒庵!你這毒婦竟也來了褒墨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤擎宝,失蹤者是張志新(化名)和其女友劉穎郁妈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绍申,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡噩咪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了极阅。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胃碾。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖筋搏,靈堂內(nèi)的尸體忽然破棺而出仆百,到底是詐尸還是另有隱情,我是刑警寧澤奔脐,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布儒旬,位于F島的核電站栏账,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏栈源。R本人自食惡果不足惜挡爵,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望甚垦。 院中可真熱鬧茶鹃,春花似錦、人聲如沸艰亮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽迄埃。三九已至疗韵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侄非,已是汗流浹背蕉汪。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逞怨,地道東北人者疤。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像叠赦,于是被迫代替她去往敵國和親驹马。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法除秀,類相關(guān)的語法糯累,內(nèi)部類的語法,繼承相關(guān)的語法册踩,異常的語法泳姐,線程的語...
    子非魚_t_閱讀 31,623評論 18 399
  • 這篇文章解釋了Java 虛擬機(JVM)的內(nèi)部架構(gòu)。下圖顯示了遵守Java SE 7 規(guī)范的典型的 JVM 核心內(nèi)...
    飲墨饗書閱讀 659評論 0 1
  • 前言 不知道大家有沒有這樣一種感覺允耿,程序員的數(shù)量井噴了借笙。可能是因為互聯(lián)網(wǎng)火了较锡,也可能是各家培訓機構(gòu)為我們拉來了大量...
    活這么大就沒飽過閱讀 2,722評論 6 26
  • 目錄 1 什么是個人知識管理 3 2 個人知識管理(PKM)和個人信息管理(PIM)的關(guān)系是什么 3 ...
    戰(zhàn)無不勝_知行合一閱讀 1,430評論 5 31
  • 被鬧鐘吵醒的我业稼,腦中還殘留著夢中的景象。 大部頭蚂蕴。低散。俯邓。怎么會夢到大學時的生活? 看看時間熔号,六點半稽鞭,身旁已經(jīng)空了。 ...
    張孤山閱讀 120評論 0 1