1. 程式人生 > 實用技巧 >JVM——字串常量池詳解

JVM——字串常量池詳解


關注微信公眾號:CodingTechWork,一起學習進步。

引言

  在Java開發中不管是前後端互動的JSON串,還是資料庫中的資料儲存,我們常常需要使用到String型別的字串。作為最常用也是最基礎的引用資料型別,JVM為String提供了字串常量池來提高效能,本篇文章我們一起從底層JVM中認識並學習字串常量池的概念和設計原理。

字串常量池由來

  在日常開發過程中,字串的建立是比較頻繁的,而字串的分配和其他物件的分配是類似的,需要耗費大量的時間和空間,從而影響程式的執行效能,所以作為最基礎最常用的引用資料型別,Java設計者在JVM層面提供了字串常量池。

實現前提

  1. 實現這種設計的一個很重要的因素是:String型別是不可變的,例項化後,不可變,就不會存在多個同樣的字串例項化後有資料衝突;
  2. 執行時,例項建立的全域性字串常量池中會有一張表,記錄著長相持中每個唯一的字串物件維護一個引用,當垃圾回收時,發現該字串被引用時,就不會被回收。

實現原理

  為了提高效能並減少記憶體的開銷,JVM在例項化字串常量時進行了一系列的優化操作:

  1. 在JVM層面為字串提供字串常量池,可以理解為是一個快取區;
  2. 建立字串常量時,JVM會檢查字串常量池中是否存在這個字串;
  3. 若字串常量池中存在該字串,則直接返回引用例項;若不存在,先例項化該字串,並且,將該字串放入字串常量池中,以便於下次使用時,直接取用,達到快取快速使用的效果。
	String str1 = "abc";
	String str2 = "abc";
	System.out.println("str1 == str2: " + (str1 == str2)); //結果:str1 == str2: true

字串常量池位置變化

方法區

  提到字串常量池,還得先從方法區說起。方法區和Java堆一樣(但是方法區是非堆),是各個執行緒共享的記憶體區域,是用於儲存已經被JVM載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。
  很多人會把方法區稱為永久代,其實本質上是不等價的,只不過HotSpot虛擬機器設計團隊是選擇把GC分代收集擴充套件到了方法區,使用永久代來代替實現方法區。其實,在方法區中的垃圾收集行為還是比較少的,這個區域的記憶體回收目標主要是針對常量池的回收和對型別的解除安裝,但是這個區域的回收總是不盡如人意的,如果該區域回收不完全就會出現記憶體洩露。當然,對於JDK1.8時,HostSpot VM對JVM模型進行了改造,將元資料放到本地記憶體

,將常量池和靜態變數放到了Java堆裡。

元空間

   JDK 1.8, HotSpot JVM將永久代移除了,使用本地記憶體來儲存類的元資料資訊,即為元空間(Metaspace)

  所以,字串常量池的具體位置是在哪裡?當然這個我們後面需要區分jdk的版本,jdk1.7之前,jdk1.7,以及jdk1.8,因為這些版本中,字串常量池因為方法區的改變而做了一些變化。

JDK1.7之前

  在jdk1.7之前,常量池是存放在方法區中的。

JDK1.7

  在jdk1.7中,字串常量池移到了堆中,執行時常量池還在方法區中。

JDK1.8

  jdk1.8刪除了永久代,方法區這個概念還是保留的,但是方法區的實現變成了元空間,常量池沿用jdk1.7,還是放在了堆中。這樣的效果就變成了:常量池和靜態變數儲存到了堆中,類的元資料及執行時常量池儲存到元空間中。

  為啥要把方法區從JVM記憶體(永久代)移到直接記憶體(元空間)?主要有兩個原因:

  1. 直接記憶體屬於本地系統的IO操作,具有更高的一個IO操作效能,而JVM的堆記憶體這種,如果有IO操作,也是先複製到直接記憶體,然後再去進行本地IO操作。經過了一系列的中間流程,效能就會差一些。非直接記憶體操作:本地IO操作——>直接記憶體操作——>非直接記憶體操作——>直接記憶體操作——>本地IO操作,而直接記憶體操作:本地IO操作——>直接記憶體操作——>本地IO操作
  2. 永久代有一個無法調整更改的JVM固定大小上限,回收不完全時,會出現OutOfMemoryError問題;而直接記憶體(元空間)是受到本地機器記憶體的限制,不會有這種問題。

變化

  1. 在JDK1.7前,執行時常量池+字串常量池是存放在方法區中,HotSpot VM對方法區的實現稱為永久代。
  2. 在JDK1.7中,字串常量池從方法區移到堆中,執行時常量池保留在方法區中。
  3. 在JDK1.8中,HotSpot移除永久代,使用元空間代替,此時字串常量池保留在堆中,執行時常量池保留在方法區中,只是實現不一樣了,JVM記憶體變成了直接記憶體。

結合程式碼

字串物件建立詳解

程式碼示例

	String str1 = "123";
	String str2 = "123";
    String str3 = "123";
    String str4 = new String("123");
    String str5 = new String("123");
    String str6 = new String("123");

結果

str1 == str2:true
str2 == str3:true
str3 == str4:false
str4 == str5:false
str5 == str6:false

jvm儲存示例

建立物件流程

對於jvm底層,String str = new String("123")建立物件流程是什麼?

  1. 在常量池中查詢是否存在"123"這個字串;若有,則返回對應的引用例項;若無,則建立對應的例項物件;
  2. 在堆中new一個String型別的"123"字串物件;
  3. 將物件地址複製給str,然後建立一個應用。

注意
若常量池裡沒有"123"字串,則建立了2個物件;若有該字串,則建立了一個物件及對應的引用。

Q&A

String str ="ab" + "cd";物件個數?

分析:若字串常量池該字串物件

  1. 字串常量池:(1個物件)"abcd";
  2. 堆:無
  3. 棧:(1個引用)str
    總共:1個物件+1個引用

String str = new String("abc");物件個數?

分析:若字串常量池該字串物件

  1. 字串常量池:(1個物件)"abc";
  2. 堆:(1個物件)new String("abc")
  3. 棧:(1個引用)str
    總共:2個物件+1個引用

String str = new String("a" + "b");物件個數?

分析:若字串常量池該字串物件

  1. 字串常量池:(3個物件)"a","b","ab";
  2. 堆:(1個物件)new String("ab")
  3. 棧:(1個引用)str
    總共:4個物件+1個引用

String str = new String("ab") + "ab";物件個數?

分析:若字串常量池該字串物件

  1. 字串常量池:(1個物件)"ab";
  2. 堆:(1個物件)new String("ab")
  3. 棧:(1個引用)str
    總共:2個物件+1個引用

String str = new String("ab") + new String("ab");物件個數?

分析:若字串常量池該字串物件

  1. 字串常量池:(1個物件)"ab";
  2. 堆:(2個物件)new String("ab"),new String("ab")
  3. 棧:(1個引用)str
    總共:3個物件+1個引用

String str = new String("ab") + new String("cd");物件個數?

分析:若字串常量池該字串物件

  1. 字串常量池:(2個物件)"ab","cd";
  2. 堆:(2個物件)new String("ab"),new String("cd")
  3. 棧:(1個引用)str
    總共:4個物件+1個引用

String str3 = str1 + str2;物件個數?

String str1 = "ab";
String str2 = "cd";
String str3 = str1 + str2;

分析:若字串常量池該字串物件

  1. 字串常量池:(2個物件)"ab","cd","abcd";
  2. 堆:無
  3. 棧:(3個引用)str1,str2,str3
    總共:2個物件+3個引用

如何指向字串池中特定的物件?

通過intern()方法。
程式碼

        String str1 = "123";
        String str2 = new String("123");
        String str3 = str2;
        System.out.println("str1 == str2:" + (str1 == str2));
        System.out.println("str1 == str3:" + (str1 == str3));

		//通過java.lang.String.intern()方法指定字串物件
        String str4 = str2.intern();
        System.out.println("str1 == str4:" + (str1 == str4));

結果

str1 == str2:false
str1 == str3:false
str1 == str4:true