深入理解Java字符串常量池
1. new String("Hello")創(chuàng)建了幾個對象
要想了解String概念,我們先從下面面試題開始
String str = new String("Hello")
思考:上面代碼創(chuàng)建幾個對象?
琳琳不假思索回答:創(chuàng)建一個對象
我直接回答琳琳說不完全對,不可能是一個,也可能是兩個,使用new 關(guān)鍵字創(chuàng)建字符串時,Java虛擬機(jī)會在字符串常量池查找有沒Hello這個字符串。演示圖如下:
- 如果有,就不會在字符串常量池中創(chuàng)建Hello該對象,直接在堆中創(chuàng)建一個Hello字符串,然后將堆中Hello對象地址返回賦值給變量str.如果沒有
- 如果常量池有,先在字符串常量池中創(chuàng)建一個'Hello'的字符串對象,然后再在堆中創(chuàng)建一個'Hello'的字符串對象,然后將堆中這個'Hello'的字符串對象地址返回賦值給變量 str。
說明:棧上主要存儲兩類數(shù)據(jù):基本數(shù)據(jù)類型的變量和對象的引用,而對象本身則存儲在堆上
琳琳問我,為什么要先在字符串常量池中創(chuàng)建對象,然后再在堆上創(chuàng)建? 這樣不是多此一舉
是的,由于字符串使用頻率很高,Java虛擬機(jī)為了減少內(nèi)存開銷和提高性能,在創(chuàng)建字符串對象時候進(jìn)行了一些優(yōu)化,特意給字符串開辟一塊空間-----字符串常量池
2. 字符串常量池的作用
琳琳又問,我們平常創(chuàng)建對象采用雙引號方式創(chuàng)建字符串對象,而不是通過new 關(guān)鍵字方式創(chuàng)建
String str = "Hello"
思考:采用雙引號方式創(chuàng)建字符串對象和new 關(guān)鍵字方式創(chuàng)建區(qū)別
String str = "Hello" 時,Java 虛擬機(jī)會先在字符串常量池中查找有沒有Hello這個字符串對象,
- 如果有,則不創(chuàng)建任何對象,直接將字符串常量池中這個Hello的對象地址返回,賦給變量 str
- 如果沒有,在字符串常量池中創(chuàng)建Hello這個對象,然后將其地址返回,賦給變量 str
Java 虛擬機(jī)創(chuàng)建了一個字符串對象 "Hello",它被添加到了字符串常量池中,同時引用變量 str 存儲在棧上,它指向字符串常量池中的字符串對象 "Hello"。這樣就省了一步,比之前高效了。
3. 舉例說明
String str = new String("Hello");
String str1 = new String("Hello");
思考:上面例子創(chuàng)建了幾個對象
創(chuàng)建三個對象,首先在字符串常量池創(chuàng)建一個,其次堆上創(chuàng)建兩個
String str = new String("spring葵花寶典");
String str1 = new String("spring葵花寶典");
思考:雙引號創(chuàng)建字符串創(chuàng)建幾個對象
創(chuàng)建一個對象,就是字符串常量中的那個對象,這樣就提高了性能
4. 字符串常量池在內(nèi)存中位置
琳琳又問,哥,字符串常量池在內(nèi)存中的什么位置呢?
我說,你這個問題問得好
分為三個時間段
Java7之前
在Java 7之前,字符串常量池位于永久代(Permanent Generation)中,而普通的字符串對象則存儲在Java堆(Java Heap)中。字符串常量池用于存儲靜態(tài)數(shù)據(jù),包括字符串常量,而堆用于存儲對象實例和數(shù)組。
當(dāng)我們創(chuàng)建一個字符串常量時,它會被儲存在永久代的字符串常量池中。如果我們創(chuàng)建一個普通字符串對象,則它將被儲存在堆中。如果字符串對象的內(nèi)容是一個已經(jīng)存在于字符串常量池中的字符串常量,那么這個對象會指向已經(jīng)存在的字符串常量,而不是重新創(chuàng)建一個新的字符串對象
Java7
需要注意的是,永久代的大小是有限的,并且很難準(zhǔn)確地確定一個應(yīng)用程序需要多少永久代空間。如果我們在應(yīng)用程序中使用了大量的類、方法、常量等靜態(tài)數(shù)據(jù),就有可能導(dǎo)致永久代空間不足。這種情況下,JVM 就會拋出 OutOfMemoryError 錯誤
Java 7 開始,為了解決永久代空間不足的問題,將字符串常量池從永久代中移動到堆中。這個改變也是為了更好地支持動態(tài)語言的運行時特性。
Java 8
在Java 8中,永久代(PermGen)被取消,取而代之的是元空間(Metaspace)。元空間是一塊本機(jī)內(nèi)存區(qū)域,與JVM內(nèi)存區(qū)域分開。它承擔(dān)了存儲類信息、方法信息、常量池信息等靜態(tài)數(shù)據(jù)的功能,與永久代的作用相似。
與永久代不同的是,元空間具有一些優(yōu)點:
- 動態(tài)調(diào)整大小:元空間的大小可以動態(tài)調(diào)整,這意味著不會因為元空間的大小限制而導(dǎo)致OutOfMemoryError錯誤。
- 使用本機(jī)內(nèi)存:元空間使用本機(jī)內(nèi)存而不是JVM堆內(nèi)存,這有助于避免堆內(nèi)存的碎片化問題,提高了內(nèi)存利用率。
- 垃圾收集與堆分離:元空間中的垃圾收集與堆中的垃圾收集是分離的。這意味著在應(yīng)用程序運行過程中進(jìn)行類加載和卸載時,不會頻繁觸發(fā)Full GC,從而減少了系統(tǒng)資源的消耗。
總的來說,Java 8中的元空間相較于永久代帶來了更好的性能和更可靠的內(nèi)存管理。