執行時常量池的再深入,從jvm的記憶體分配角度談談這道字串常量池的面試題。

詩水人間發表於2020-12-14

此前我寫過另外一篇關於字串常量池的面試題

執行時常量池的一道面試題(jdk8環境)

本篇部落格的內容能證明我上一篇部落格中的推論

面試題原題:

public class TestDemo {
    
    @Test
    public void test01() {
        String str1 = new StringBuilder("ja").append("va").toString();
        String str2 = str1.intern();
        System.out.println(str1 == str2);// 結果是false

        String str3 = new StringBuilder("hello").append("world").toString();
        String str4 = str3.intern();
        System.out.println(str3 == str4);// 結果是true
    }
}

關於String.intern()的正確理解:以str3.intern();為例,如果字串常量中沒有"helloworld"這個字串,則將這個字串物件存入字串常量池中,然後返回這個字串物件。如果有則直接返回字串常量池中的對應的那個物件,也就是被str4引用。

關於上面這句話需要注意!

在物件str3的建立到輸出true這個過程中,如果按照我的解釋:

第一行程式碼:

String str3 = new StringBuilder(“hello”).append(“world”).toString();
在堆中的Eden區根據已有字串常量"hello""world"建立一個新的字串物件,"helloworld"此時這個物件只是在堆中的Eden區,而字串常量池則在堆的Old區。

第二行程式碼:

String str4 = str3.intern();

str3呼叫intern方法,因為字串常量池中沒有"helloworld"物件,則將這個物件存入字串常量池中,然後將這個字串物件返回。

細節:

很多人在這塊沒有搞懂,為什麼我要這樣解釋,下面我娓娓道來。
這得涉及到 jvm 的記憶體管理的知識點。
當我們建立一個物件的時候,意味著需要申請一塊空間用來存放這個新建立的物件。可這裡有一個問題,jvm 如何管理記憶體的?
jvm 返回的用於分配新建物件的地址區域的首地址,這塊區域(指分配給物件的記憶體)肯定是不能被其它類使用的,不然記憶體豈不是亂來了?

換句話說jvm 是有做記憶體管理的,下面借用《深入理解java虛擬機器》原文作為說明
在這裡插入圖片描述
  意思就是說虛擬機器的記憶體管理我們可以用一張表來記錄哪些記憶體是被使用的,哪些是空閒的,如果要進行gc,也很簡單,就是對被使用的記憶體進行垃圾回收,然後涉及到垃圾回收演算法,標記清除,標記整理,標記複製,其中後面兩種可以將記憶體整理到一塊,可以避免產生大量記憶體碎片,造成記憶體浪費。

  那對於第一行到第二行程式碼就可以這樣完整的解釋

  第一行程式碼:jvm根據這種記憶體管理表,找到一塊空閒的空間,然後將"helloworld"物件存到這塊空間中,然後將這個地址在表中記錄下來(表示這塊區域就是這個物件)。

  而String str3 = new StringBuilder(“hello”).append(“world”).toString();的操作,str3的賦值底層的操作相當於將指標指向了表中的這條記錄,而值才是這個"helloworld"物件的真實地址,意味著str3指向的是表中的記錄,這個值在虛擬機器棧中是如果沒有其它賦值操作是不會改變的。而當我們移動物件的時候,只需要更新一下表,那麼虛擬機器棧中這個str3變數依然能找到移動後的物件。

畫兩張圖說明這個過程

下面是建立物件的過程
在這裡插入圖片描述

  下面是呼叫intern()方法做的事情,將物件移動,更新記憶體管理表中物件的記憶體位置。
這樣就做到了物件的移動後還能通過同一個str可以訪問到移動後的那個物件。

  關於物件移動其實在gc的時候也會導致物件移動,原理和這個差不多。只不過在這個案例中我們是通過navtive宣告的intern()方法完成物件的移動,而不是gc導致物件移動。

在這裡插入圖片描述

會過頭來再看看這道面試題

  這道題目就很簡單了

  str4做的事情就是增加了一條引用記錄相當於下面的截圖,顯然兩個物件的地址是同一個(都是0xffffffff),因此會返回true

在這裡插入圖片描述


  上面的已經講完,作為補充,把上面那道面試題的另外一種情況關於"java"返回false的也用圖來解釋一遍,就是

  “java”字串再Version類中有定義過例如我的jdk中關於Version的部分程式碼

  除了"java",還有"1.8.0_201""Java(TM) SE Runtime Environment""1.8.0_201-b09"這些字串常量也是再字串常量池中已有(可以自己根據jdk版本找到對應的這些資訊)

public class Version {
    private static final String launcher_name = "java";
    private static final String java_version = "1.8.0_201";
    private static final String java_runtime_name = "Java(TM) SE Runtime Environment";
    private static final String java_profile_name = "";
    private static final String java_runtime_version = "1.8.0_201-b09";
    ......//省略其它定義
}

  而我們執行str1的第一行程式碼String str1 = new StringBuilder(“ja”).append(“va”).toString();就是根據已有字串"ja"、"va"再Eden區中建立一個"java"字串物件,注意字串常量池中已經有了這個字元物件,這是兩個不同的物件,連存在記憶體的位置也不同。
在這裡插入圖片描述

在這裡插入圖片描述
  因此str1==str2返回的是false,因為兩個地址不同,對於非基本型別==比較的是地址,也就是比較0xf1f2f3f40xffffffff

相關文章