JAVA虛擬機器-方法區與字串常量池

半夏之沫發表於2022-11-24

前言

方法區(Method Area)是執行緒共享的一塊記憶體區域,JVM載入的型別資訊常量靜態變數即時編譯器編譯後的程式碼快取等資料均存放於方法區。

執行時常量池(Runtime Constant Pool)是方法區的一部分,在Class檔案中有一部分內容為常量池表(Constant Pool Table),用於存放編譯期生成的各種字面量與符號引用,這部分內容在Class檔案被載入到JVM後會被存放在執行時常量池中。

字串常量池中存放字串字面量,在JDK1.8中,字串常量池存在於堆中。

本篇文章將對JDK1.8中的方法區,執行時常量池和字串常量池的區域分佈進行說明,並著重對字串常量池進行分析以探究new一個字串物件時到底會在堆上建立幾個物件。

JDK版本:1.8
參考資料:《深入理解Java虛擬機器第三版》

正文

一. 方法區的區域分佈

方法區只是一個邏輯概念,在JDK1.8中方法區的具體實現為元空間,而元空間使用的是本地記憶體。在JDK1.8中,方法區,執行時常量池和字串常量池的區域分佈示意圖如下所示。

即字串常量池存在於堆中,並且如果要設定方法區大小,需要使用-XX:MaxMetaspaceSize=指令進行設定。

JDK1.8中使用元空間作為方法區的實現以替代永久代(PermGen),有如下原因。

  • 永久代作為方法區的實現時,字串常量池存在於執行時常量池中,即字串常量池存在於方法區中,而方法區只有Full GC時才會被清理,因此容易出現由於字串常量池導致的記憶體溢位;
  • 型別資訊等資料大小不容易確定,將其存放到本地記憶體更為合適。

二. 字串常量池

首先回答那個經典的問題:new一個字串物件會建立幾個物件。答案是一個或者兩個

字串常量池中會儲存字串字面量,字串字面量本質就是物件,當在程式碼中出現如下程式碼。

String str = "sakura";

如果字串常量池中已經存在sakura這個字串字面量,那麼str會指向字串常量池中的sakura字串字面量,反之,則會先將sakura這個字串字面量新增到字串常量池中,然後再將str指向字串常量池中的sakura字串字面量。

更甚一步,其實只要程式碼中出現雙引號括起來的字串,那麼就會去字串常量池中尋找對應的字串字面量,如果尋找不到,則建立字串字面量並新增到字串常量池中。

現在如果在程式碼中出現如下程式碼。

String str = new String("sakura");

首先出現了雙引號括起來的sakura字串,所以就會去字串常量池中尋找對應的字串字面量,如果尋找不到,則建立sakura字串字面量並新增到字串常量池中,如果尋找到,則直接使用字串常量池中的sakura字串字面量。最後,會在堆上建立一個字串物件,str會指向堆上建立出來的字串物件。所以new一個字串物件時,可以肯定的是一定會在堆上建立一個字串物件,但是字串常量池中是否會建立一個字串字面量,要取決於字串字面量之前是否已經存在,已經存在則不會再重複建立。所以new一個字串物件會建立幾個物件的答案是一個或者兩個。

為了加深理解,考慮如下的示例。

public class StringTest {

    public static void main(String[] args) {
        String str1 = new String("sakura") + new String("sakura");  //步驟1

        String str2 = "sakurasakura";  //步驟2

        System.out.println(str1 == str2);  //步驟3
    }

}

當執行完步驟1後,堆上的情況如下所示。

執行完步驟2後,堆上的情況如下所示。

所以最終步驟3的列印結果一定是false

3. String的intern()方法

首先考慮如下的示例。

public class StringTest {

    public static void main(String[] args) {
        String str1 = new String("sakura") + new String("sakura");  //步驟1
        
        str1.intern();  //步驟2

        String str2 = "sakurasakura";  //步驟3

        System.out.println(str1 == str2);  //步驟4
    }

}

上述示例和第2小節中的示例差不多,只不過多了一步str1.intern()Stringintern()方法會根據當前字串物件的值去字串常量池中進行匹配,如果字串常量池中存在字串字面量的值與當前字串物件的值相等,則返回這個字串字面量的地址,如果字串常量池中不存在字串字面量的值與當前字串物件的值相等,則在字串常量池中註冊一個引用並指向當前字串物件,並最後返回當前字串物件的地址。那麼上述示例中,執行完步驟2後,堆上的情況如下所示。

執行完步驟3後,堆上的情況如下所示。

所以最終步驟4的列印結果一定是true

透過上述示例可以知道,字串常量池中除了儲存字串字面量以外,還會儲存指向堆上的字串物件的引用。

總結

方法區用於存放JVM載入的型別資訊常量靜態變數即時編譯器編譯後的程式碼快取等資料,JDK1.8中使用元空間作為方法區的實現,元空間使用的是本地記憶體。字串常量池中會儲存字串字面量,字串字面量本質就是物件,當new一個字串物件時,一定會在堆上建立一個字串物件,但是字串常量池中是否會建立一個字串字面量,要取決於字串字面量之前是否已經存在,已經存在則不會再重複建立,所以new一個字串物件會建立幾個物件的答案是一個或者兩個。

相關文章