博客 / 詳情

返回

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

大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈

前言

方法區(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一個字符串對象會創建幾個對象的答案是一個或者兩個。


大家好,我是半夏之沫 😁😁 一名金融科技領域的JAVA系統研發😊😊
我希望將自己工作和學習中的經驗以最樸實最嚴謹的方式分享給大家,共同進步👉💓👈
👉👉👉👉👉👉👉👉💓寫作不易,期待大家的關注和點贊💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓關注微信公眾號【技術探界】 💓👈👈👈👈👈👈👈👈
user avatar lankerens 頭像 fulng 頭像
2 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.