聊聊各種常量池

今天你做題了嗎發表於2020-07-14

學習JVM的時候經常會遇到各種常量池,不同版本的JDK它們的儲存位置也不同,這篇隨筆就整理下幾種常見的常量池,以JDK1.8為主。先看一張儲存示意圖,裡面涉及1.8和1.6。

 

常量池是儲存在方法區中的,比如我們有這樣一段程式碼:

public class Demo {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

我們對Demo.class“進行反彙編得到具體位元組碼資訊,前面這一段是類的元資訊,包括類名、修改時間、訪問修飾關鍵字等。

接著我們可以看到 Constant pool:,這就是常量池,每一個符號後面引用了其他符號或者表示具體的資訊。其實常量池就是一張表,虛擬機器指令根據這張常量表找到要執行的類名、方法名、引數型別、字面量等資訊。

執行時常量池:是方法區的一部分,JDK1.8方法區位於系統記憶體中。當類被載入到記憶體時,那麼原先的常量池資訊就會放入執行時常量池中,並且將 #1 這些符號地址變為直接引用。

字串常量池:也就是StringTable,1.8是儲存在堆中。用一個簡單的例子來說明字串物件的各種關係:

public class Demo {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
        System.out.println(s3 == s4); // false
        String s5 = "a" + "b";
        System.out.println(s3 == s5); // true
    }
}

編譯這段程式的時候a、b、ab這些都是執行時常量池中的符號,還沒變成Java 字串物件。當執行到“String s1 = "a"; ”這行程式碼時,ldc會將a符號變為 “a” 字串物件,如果串池中沒有"a"物件,“a”物件就會被放入StringTable裡。s2、s3邏輯類似,最終串池裡面的物件為["a", "b", "ab"]。

s4將s1、s2變數拼接,底層位元組碼的執行邏輯是在第9行建立了 StringBuilder 物件,13行是呼叫無參構造方法初始化類。17、21行呼叫了 StringBuilder.append 方法進行拼接,24行呼叫StringBuilder.toString方法,這個方法底層是生成一個新的字串即將用拼接的值作為新字串物件的值。s3是在串池中,s4是一個新的物件儲存在堆中,比較s3 == s4 輸出值應該是false。

再看s5將兩個字串常量進行拼接,這時候JVM並不會像拼接變數那樣建立物件,而是直接到串池中找到物件 “ab”,所以 “System.out.println(s3 == s5);” 輸出值為true。這是因為 javac 在編譯期間認為 "a" 、"b"是定值不會再改變, 所以直接得到結果"ab"。

 

其實我們可以使用 intern方法,主動將串池中還沒有的字串物件放入串池。

但是如果在程式開始時就將“ab”放入串池,再比較 s == "ab“ 就會返回false。

 

相關文章