JVM字符串常量池StringTable的具體使用
一、StringTable為什么要調(diào)整
jdk7之前,hotspot對于方法區(qū)的實現(xiàn)是永久代,常量池包括字符串常量池放于永久代中;
jdk7時,hotspot將字符串常量池(還有靜態(tài)變量)放在了堆中。有一點“去永久代”的苗頭
jdk8之后,hotspot取出永久代,取而代之的是使用本地內(nèi)存的元空間。字符串常量池還是在堆中。
為什么要將字符串常量池StringTable放在堆中?
jdk7中將StringTable放到了堆空間中,因為永久代的回收效率很低。在fullGC的時候才觸發(fā),而fullGC是老年代空間不足,永久代不足時才觸發(fā),觸發(fā)次數(shù)較少,甚至在開發(fā)中我們要避免出現(xiàn)fullGC。這就導(dǎo)致了StringTable回收效率不高,而我們開發(fā)中會創(chuàng)建大量的字符串,回收效率低,導(dǎo)致永久代內(nèi)存不足。放到堆里,能及時回收內(nèi)存。
二、String的基本特性
jdk8及以前,內(nèi)部定義了final char[] value用于存儲字符串數(shù)據(jù)
JDK9時改為byte[] + 字符類型標記,為什么做出這個改變呢?
char數(shù)組一個char占16bits(兩個字節(jié)),String是堆空間的主要部分,大部分是latin-1字符,一個字節(jié)就夠了,這樣會有一半空間浪費。所以采用byte數(shù)組+字符串類型,如果是中文等UTF-16 的用兩個字節(jié)存儲。
StringBuffer,StringBuilder同樣做了修改
String為什么不可變?
因為底層數(shù)組被final修飾。而且其類自身也被final修飾,這就導(dǎo)致了不能通過繼承去修改其內(nèi)部結(jié)構(gòu)。保證了其不可變性。
- 當字符串重新賦值,需要重寫指定內(nèi)存區(qū)域賦值,不能使用原有的value進行賦值
- 當對現(xiàn)有的字符串進行連接操作時,也需要重新指定內(nèi)存區(qū)域賦值,不能對使用原有的value進行賦值
- 當調(diào)用String的replace方法修改指定字符或字符串時,也需要重新指定內(nèi)存區(qū)域賦值,不能使用原有的value進行賦值。
字符串常量池中不會存儲相同的字符串的
String的String pool是一個固定大小的HashTable,默認大小長度是1009,如果放進String Pool的String非常多,就會造成Hash沖突嚴重,從而導(dǎo)致鏈表會很長,而鏈表長了,直接影響就是調(diào)用String.intern時性能會大幅下降
- -XX:StringTableSize可設(shè)置StringTable的大小
- JDK6固定1009,jdk7中StringTable默認的長度是60013,JDK8時默認是60013,1009是可設(shè)置的最小值
三、String的內(nèi)存分配
Java語言中有8種基本數(shù)據(jù)類型和一種比較特殊的類型String,這些類型為了使他們再運行過程中速度更快,更節(jié)省內(nèi)存,都提供了一種常量池的概念
String的常量池比較特殊,主要使用方法有兩種
- 直接使用雙引號,聲明出來的String對象會直接存儲在常量池中
- 如果不是雙引號聲明的String對象,可以使用String提供的intern()方法
jdk6及之前,字符串常量池存在永久代
jdk7中,字符串常量池調(diào)整到Java堆中,調(diào)優(yōu)時僅需調(diào)整堆大小就可以
四、字符串拼接操作
常量與常量的拼接結(jié)果在常量池,原理是編譯期優(yōu)化
只要其中有一個變量,拼接結(jié)果就在堆中(常量池以外的堆),變量的拼接原理是StringBuilder
String res = s1 + s2; // 實際上是StringBuilder s = new StringBuilder().append(s1).append(s2); // 然后調(diào)用s.toString();
如果拼接的結(jié)果調(diào)用intern方法,則主動將常量池中還沒有的字符串對象放入池中,并返回此對象地址
字符串拼接操作不一定使用的是StringBuilder如果拼接符號左右兩邊都是字符串常量或常量引用,則仍然使用編譯期優(yōu)化,即非StringBuilder的方式
針對final修飾類,方法,基本數(shù)據(jù)類型,引用數(shù)據(jù)類型變量的結(jié)構(gòu)時,能使用final盡量使用上
五、intern()方法
jdk1.6中,將這個字符串對象放入串池
- 如果串池中有,則并不會放入,返回已有串池中的對象的地址,
- 如果沒有,會把對象復(fù)制一份,放入串池,并返回串池中的對象地址
jdk1.7起,將這個字符串對象嘗試放入串池
- 如果串池中有,則并不會放入,返回已有的串池中的對象的地址
- 如果沒有,則會把對象的引用地址復(fù)制一份,放入串池,并返回串池中的引用地址
例子:
前置知識
newString("ab")會創(chuàng)建幾個對象?
2個對象,查看字節(jié)碼驗證。一個是常量池ab,一個是new出來在堆空間。(前提是常量池沒有ab)
new String("a")+new String("b")?
- 對象1,有拼接操作就newStringBuilder
- 對象2,new一個String
- 對象3,常量池a
- 對象4,new String
- 對象5,常量池b
- 對象6,StringBuilder,toString方法會new String返回
- 注意:toString方法這里new String并不會向字符串常量池中放入"ab",不像我們平時上面一樣會放入一個“ab”在常量池中。
結(jié)果
jdk6 false false
jdk7/8 false true
分析:
jdk6的intern會復(fù)制一份"1"放入字符串常量池。但是new的時候其實已經(jīng)放入常量池了,所以這里intern沒啥用,此時s2拿到的就是常量池的那一份。而s是指向堆中new出來的String對象,所以為false
s3這里intern的時候常量池沒有“11”,所以會復(fù)制一份放入常量池,此時s4拿到的就是常量池的那一份。而s3指向堆中new出來的String對象,所以為false
jdk7/8的intern是復(fù)制引用地址放入字符串常量池,但是new的時候其實已經(jīng)放入常量池了,所以這里intern沒啥用,此時s2拿到的就是常量池的那一份。所以為false
s3這里intern的時候常量池沒有“11”,所以會復(fù)制引用放入常量池,此時s4拿到的就是常量池的那一份引用。而s3也指向堆中new出來的String對象,所以為true
六、Stringtable的垃圾回收
-XX:+PrintStringTableStatistics
七、G1中String去重操作
背景:對許多Java應(yīng)用,做的測試結(jié)果如下
- 堆存貨數(shù)據(jù)集合里面String對象占了25%
- 堆存活數(shù)據(jù)集合里面重復(fù)的String對象有13.5%
- String對象的平均長度是45
許多大規(guī)模的Java應(yīng)用的瓶頸在于內(nèi)存。Java堆中存活的數(shù)據(jù)集合差不多25%是String對象,這里差不多一半的String對象是重復(fù)的, 重復(fù)是指equals方法=true,堆上重復(fù)的String對象必然是一種內(nèi)存的浪費。G1垃圾收集器中實現(xiàn)自動持續(xù)對重復(fù)的String對象進行去重,這樣避免浪費。
到此這篇關(guān)于JVM字符串常量池StringTable的具體使用的文章就介紹到這了,更多相關(guān)JVM字符串常量池StringTable內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文教會你用mybatis查詢數(shù)據(jù)庫數(shù)據(jù)
MyBatis本身是一個數(shù)據(jù)庫連接框架,可以認為是JDBC的升級版,下面這篇文章主要給大家介紹了關(guān)于mybatis查詢數(shù)據(jù)庫數(shù)據(jù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2022-04-04postman?如何實現(xiàn)傳遞?ArrayList?給后臺
這篇文章主要介紹了postman?如何實現(xiàn)傳遞?ArrayList給后臺,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12springboot項目實現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter
這篇文章主要介紹了springboot項目實現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter,本文分步驟結(jié)合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-06-06Java數(shù)據(jù)類型之引用數(shù)據(jù)類型解讀
這篇文章主要介紹了Java數(shù)據(jù)類型之引用數(shù)據(jù)類型,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07基于SpringBoot實現(xiàn)定時發(fā)送郵件過程解析
這篇文章主要介紹了基于SpringBoot實現(xiàn)定時發(fā)送郵件過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06

Java連接mysql數(shù)據(jù)庫的詳細教程(推薦)