new String的值是存儲在堆中還是常量池呢?

面試問:new String的值是存儲在堆中還是常量池呢?

在Java中,我們知道new出來的對象會存儲在堆中,那new String在JMM中是怎么存儲的呢?

稍微有點基礎的同學都知道,String x = "常量池"; 這段代碼呢,Java會將字符串存儲在常量池中。
但是String x = new String("常量池"); 這段代碼,字符串存儲在哪里呢?尤其這個就變成了面試同學的坑。

  • 別急,我們先看一段代碼,和當時棧中的局部變量表:
new String在JMM的存儲位置.png
// 代碼如下:
char[] f = {'a', 's', 'd'};

String a = "asd"; // 常量池
String b = new String("asd"); // 字符串形式new
String c = new String(f); // 數組形式new
String d = new String(f).intern(); // 數組形式new,使用intern方法
String b2 = new String("asd").intern(); // 字符串形式new

System.out.println(a + b + c + d); // 斷點打在這里,只是為了防止jvm編譯對代碼優化

System.out.println(a == b); // false
System.out.println(a == b2); // true
System.out.println(a == d); // true
System.out.println(b == d); // false
System.out.println(c == d); // false

提出疑問:

  • 首先,假定a變量存儲于常量池中(當然這是毋庸置疑的)。
  • 那么圖中b變量和a指向了同一個數組的內存地址,“==”雙等號比較的是內存地址,理論上a==b為true,但為何卻是 false?
  • 圖中d變量也和a指向了同一個數組的內存地址,卻經過intern方法后,a==d卻是true?
  • c變量是以數組形式生成的String,和b變量的區別在于使用了不同的構造函數,難道是構造函數原因?

帶著疑問,我們來一步步解惑:

  • 首先,不要考慮new String存儲在哪里,我們先解釋為何 a==b(或c)為false,而加了intern方法的a==d(或b2)為true?

intern方法的作用:
When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the #equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

意思是說:調用intern方法時,如果常量池中包含equals(Object)方法相同的字符串,則返回常量池中的字符串。 否則,將此String對象添加到常量池中,并返回對此String對象的引用。實則這個引用就是常量池中字符串;

所以,我們在對一個new出來的String的對象使用該方法時,它返回的是常量池中值。
這也就解釋了為何 d(或b2)變量等于a變量,因為他倆都為常量池中同一個地址,但還不能證明new String是如何存放。

  • 根據上面結論,那么b變量不等于a變量,則代表不是指向常量池中地址,那為何圖中a和b的局部變量表中卻是同一個地址,那不是矛盾了嗎?

好,那么看看String類的幾個構造函數吧:


String的一個構造.png

因為String類型是一個不可變類型,而以new String("字符串")這種方式創建字符串時,當你傳入實參,實參其實已經在常量池創建了一個字符串original,等同于String original = "字符串";然后 this.value = original.value 是將常量池中字符串的char數組的引用指向了將要創建的對象的value屬性,切記只是引用,而真正的值是在常量池中。
這也就是為何局部變量表指向同一個地址的原因,而引用呢則是放在了堆中的String對象中。

  • 而以數組形式生成的String的c變量為何不放在字符串常量池中?

因為char數組是不會直接放入常量池的,所以在這個構造中:this.value = Arrays.copyOf(value, value.length),是對實參變量做了拷貝,存儲在堆中;

如果對c變量做intern,通過實例的 equals(Object) 對池中的數據進行比較,如果字符串常量池中有與 c 內容相同的字符串,則直接返回該字符串的引用。如果沒有相同實例,則保存其值再將指向池中字符串的引用返回。

注意編碼規范:不要對動態char數組創建String字符串時添加intern方法,小心把你方法區干沒,導致堆外內存溢出。這可能也是該構造的字符串不放常量池的原因吧。

  • 其實javap命令也能看出來,向常量池申請了變量然后壓入棧。

  • new String形式本質上其實都是創建在堆中的,其本質上這個區別應該叫做String中value數組存儲在堆還是常量池會更準確。(請看下圖)


    String中value數組指向圖.png

總結:

  1. 對于直接聲明的字符串,形如:String x = ""; 則變量x直接指向常量池中;
  2. 對于new出來的字符串,new String(""); 則存儲于堆中,但存儲的是指向常量池的引用,對于new出來的字符串數組,則存儲value的值指向堆內存;
  3. intern方法可以向常量池存儲字符串,并返回一個常量池的引用對象;

實驗環境:jdk1.8

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容