JVM——字符串常量池詳解

技術公衆號:後端技術解憂鋪
關注微信公衆號:CodingTechWork,一塊兒學習進步。java

引言

  在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,由於這些版本中,字符串常量池由於方法區的改變而作了一些變化。jvm

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");

結果spa

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
相關文章
相關標籤/搜索