執行時常量池的再深入,從jvm的記憶體分配角度談談這道字串常量池的面試題。
此前我寫過另外一篇關於字串常量池的面試題
本篇部落格的內容能證明我上一篇部落格中的推論
面試題原題:
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
,因為兩個地址不同,對於非基本型別==
比較的是地址,也就是比較0xf1f2f3f4
、0xffffffff
相關文章
- 徹底搞清楚class常量池、執行時常量池、字串常量池字串
- 深入探究JVM之記憶體結構及字串常量池JVM記憶體字串
- 字串常量池字串
- java中字串常量池的用法Java字串
- java的常量池Java
- 好好說說Java中的常量池之Class常量池Java
- 從 JVM 記憶體模型談執行緒安全JVM記憶體模型執行緒
- 記憶體池、程式池、執行緒池記憶體執行緒
- Java虛擬機器--方法區(執行時常量池)Java虛擬機
- 正確理解和使用JAVA中的字串常量池Java字串
- JVM常量池Constant pool結構速查JVM
- 淺談執行緒池(上):執行緒池的作用及CLR執行緒池執行緒
- [轉帖]深入JVM - Code Cache記憶體池JVM記憶體
- 淺談JVM記憶體分配與垃圾回收JVM記憶體
- Java常量池解析與字串intern簡介Java字串
- 深入理解JVM(③)再談執行緒安全JVM執行緒
- 淺談執行緒池(中):獨立執行緒池的作用及IO執行緒池執行緒
- Java中的String與常量池Java
- 淺析Java常量池Java
- Java String常量池Java
- java常量池技術Java
- 言簡意賅——總結Java記憶體區域和常量池Java記憶體
- 從JS的執行機制的角度談談作用域JS
- 從原始碼的角度解析執行緒池執行原理原始碼執行緒
- 從字串到常量池,一文看懂String類設計字串
- 從萌新的角度理解JVM記憶體管理JVM記憶體
- java基礎:String — 字串常量池與intern(二)Java字串
- JAVA虛擬機器-方法區與字串常量池Java虛擬機字串
- 談談這幾個常見的多執行緒面試題執行緒面試題
- 談談OKHttp的幾道面試題HTTP面試題
- 關於執行緒池的面試題執行緒面試題
- 詳談執行緒池的理解和應用執行緒
- Java常量池理解與總結Java
- Oracle記憶體分配中的子池(Subpool)--ORA-04031Oracle記憶體
- 一頁解決字串常量池相關疑難雜症字串
- 檢測一個字串是否在jvm的常量池中字串JVM
- Java String類,字串常量池,建立方法,字串的獲取,擷取,轉換,分割。Java字串
- JVM記憶體分配JVM記憶體