1. 程式人生 > >java中的字串常量池,棧和堆的概念

java中的字串常量池,棧和堆的概念

問題:String str = new String(“abc”),“abc”在記憶體中是怎麼分配的?    答案是:堆,字串常量區。

題目考查的為Java中的字串常量池和JVM執行時資料區的相關概念。

"abc"為字面量物件,其儲存在堆記憶體中而字串常量池則儲存的是字串物件的一個引用。

Java中的字串常量池

Java中字串物件建立有兩種形式,一種為字面量形式,如String str = "droid";,另一種就是使用new這種標準的構造物件的方法,如String str = new String("droid");,這兩種方式我們在程式碼編寫時都經常使用,尤其是字面量的方式。然而這兩種實現其實存在著一些效能和記憶體佔用的差別。這一切都是源於JVM為了減少字串物件的重複建立,其維護了一個特殊的記憶體,這段記憶體被成為字串常量池

或者字串字面量池。

工作原理

當代碼中出現字面量形式建立字串物件時,JVM首先會對這個字面量進行檢查,如果字符串常量池中存在相同內容的字串物件的引用,則將這個引用返回,否則新的字串物件被建立,然後將這個引用放入字串常量池,並返回該引用

邏輯圖如下:

舉例說明

字面量建立形式

String str1="droid";

JVM檢測這個字面量,這裡我們認為沒有內容為droid的物件存在。JVM通過字串常量池查詢不到內容為droid的字串物件存在,那麼會建立這個字串物件,然後將剛建立的物件的引用放入到字串常量池中,並且將引用返回給變數str1。

如果接下來有這樣一段程式碼:

  1. String str2="droid";  

同樣JVM還是要檢測這個字面量,JVM通過查詢字串常量池,發現內容為”droid”字串物件存在,於是將已經存在的字串物件的引用返回給變數str2。注意這裡不會重新建立新的字串物件。

驗證是否為str1和str2是否指向同一物件,我們可以通過這段程式碼

  1. System.out.println(str1==str2);  

輸出:True.

使用new建立

  1. String str3=new String("droid");  

當我們使用了new來構造字串物件的時候,不管字串常量池中有沒有相同內容的物件的引用,新的字串物件都會建立。因此我們使用下面程式碼測試一下,

  1. System.out.println(str1==str3);  

結果返回:False 表明這兩個變數指向的為不同的物件.

intern

對於上面使用new建立的字串物件,如果想將這個物件的引用加入到字串常量池,可以使用intern方法。
呼叫intern後,首先檢查字串常量池中是否有該物件的引用,如果存在,則將這個引用返回給變數,否則將引用加入並返回給變數。
  1. String str4=str3.intern();  
  2. System.out.println(str4==str1);  
  1. 輸出結果為True。  

疑難問題

前提條件?

字串常量池實現的前提條件就是Java中String物件是不可變的,這樣可以安全保證多個變數共享同一個物件。如果Java中的String物件可變的話,一個引用操作改變了物件的值,那麼其他的變數也會受到影響,顯然這樣是不合理的。

引用 or 物件

字串常量池中存放的是引用還是物件,這個問題是最常見的。字串常量池存放的是物件引用,不是物件。在Java中,物件都建立在堆記憶體中。

關於驗證請參考原文

優缺點

字串常量池的好處就是減少相同內容字串的建立,節省記憶體空間。

如果硬要說弊端的話,就是犧牲了CPU計算時間來換空間。CPU計算時間主要用於在字串常量池中查詢是否有內容相同物件的引用。不過其內部實現為HashTable,所以計算成本較低。

GC回收?

因為字串常量池中持有了共享的字串物件的引用,這就是說是不是會導致這些物件無法回收?

首先問題中共享的物件一般情況下都比較小。據我查證瞭解,在早期的版本中確實存在這樣的問題,但是隨著弱引用的引入,目前這個問題應該沒有了。

intern使用?

關於使用intern的前提就是你清楚自己確實需要使用。比如,我們這裡有一份上百萬的記錄,其中記錄的某個值多次為美國加利福尼亞州,我們不想建立上百萬條這樣的字串物件,我們可以使用intern只在記憶體中保留一份即可。關於intern更深入的瞭解請參考深入解析String#intern

Java中的堆和棧的區別

當一個人開始學習Java或者其他程式語言的時候,會接觸到堆和棧,由於一開始沒有明確清晰的說明解釋,很多人會產生很多疑問,什麼是堆,什麼是棧,堆和棧有什麼區別?更糟糕的是,Java中存在棧這樣一個後進先出(Last In First Out)的順序的資料結構,這就是java.util.Stack。這種情況下,不免讓很多人更加費解前面的問題。事實上,堆和棧都是記憶體中的一部分,有著不同的作用,而且一個程式需要在這片區域上分配記憶體。眾所周知,所有的Java程式都執行在JVM虛擬機器內部,我們這裡介紹的自然是JVM(虛擬)記憶體中的堆和棧。

區別

java中堆和棧的區別自然是面試中的常見問題,下面幾點就是其具體的區別

各司其職

最主要的區別就是棧記憶體用來儲存區域性變數方法呼叫而堆記憶體用來儲存Java中的物件無論是成員變數,區域性變數,還是類變數,它們指向的物件都儲存在堆記憶體中。

獨有還是共享

棧記憶體歸屬於單個執行緒,每個執行緒都會有一個棧記憶體,其儲存的變數只能在其所屬執行緒中可見,即棧記憶體可以理解成執行緒的私有記憶體。 而堆記憶體中的物件對所有執行緒可見。堆記憶體中的物件可以被所有執行緒訪問。

異常錯誤

如果棧記憶體沒有可用的空間儲存方法呼叫和區域性變數,JVM會丟擲java.lang.StackOverFlowError。 而如果是堆記憶體沒有可用的空間儲存生成的物件,JVM會丟擲java.lang.OutOfMemoryError。

空間大小

棧的記憶體要遠遠小於堆記憶體,如果你使用遞迴的話,那麼你的棧很快就會充滿。如果遞迴沒有及時跳出,很可能發生StackOverFlowError問題。 你可以通過-Xss選項設定棧記憶體的大小。-Xms選項可以設定堆的開始時的大小,-Xmx選項可以設定堆的最大值。

這就是Java中堆和棧的區別。理解好這個問題的話,可以對你解決開發中的問題,分析堆記憶體和棧記憶體使用,甚至效能調優都有幫助。