詳解JVM 中的StringTable
是什么
字符串常量池是 JVM中的一個(gè)重要結(jié)構(gòu),用于存儲JVM運(yùn)行時(shí)產(chǎn)生的字符串。在JDK7之前在方法區(qū)中,存儲的是字符串常量。而字符串常量池在 JDK7開始移入堆中,隨之而來的是除了存儲字符串常量外,還可以存儲字符串引用(因?yàn)樵诙阎校枚阎械淖址A亢芊奖?,所以可以存儲引用)。這使得很多字符串的操作在 JDK7中和在之前的版本中執(zhí)行是不同的結(jié)果。這也是為什么字符串相關(guān)的問題是如此具有迷惑性的原因之一。
底層
String:在 JDK9之前,String底層是使用 char數(shù)組來存儲字符串?dāng)?shù)據(jù)的,而在 JDK9開始,使用 byte數(shù)組+編碼來代替 char數(shù)組,這是為了節(jié)省空間,因?yàn)椴煌幋a的數(shù)據(jù)占空間不一樣,很多單位數(shù)據(jù)只需要一個(gè) byte(8字節(jié)) 就可以存儲,而使用 char(16字節(jié))就會(huì)浪費(fèi)多余的空間。
字符串常量池:底層使用 HashTable來存儲字符串,在 JDK6 HashTable的數(shù)組長度是1006,JDK7開始變成了 60013,這是為了避免存儲字符串過多導(dǎo)致鏈表長度過長從而查詢效率降低。可以使用參數(shù) -XX:StringTableSize=來設(shè)置 StringTable數(shù)組的長度。
常見問題字符串相加
1、對于字符串常量相加,編譯器會(huì)優(yōu)化成直接相加。
如 String ss = "a" + "b",在編譯器的優(yōu)化下,實(shí)際上只會(huì)創(chuàng)建一個(gè) "ab" 字符串。
而 final String s1 = "a"; String s2 = s1+"b",除了創(chuàng)建字符串 "a"外,只會(huì)創(chuàng)建 "ab"。
操作相關(guān)字符串如下:
可以看到只對字符串 "a"、"ab"進(jìn)行了入池操作(ldc)
2、對于包含字符串變量的相加,不會(huì)在字符串常量池中創(chuàng)建對應(yīng)的字符串。
如 String s1 = "a"; String s2 = s1 + "b",執(zhí)行完后字符串常量池中只會(huì)包含 "a"、"b" 字符串。
對于 s1 + "b",下面是其字節(jié)碼操作
可以看到,相加操作實(shí)際上是調(diào)用 StringBuilder的append方法進(jìn)行字符串拼接,然后調(diào)用它的 toString方法獲取返回值保存輸出,期間并沒有入池操作(ldc)。
由此得出的優(yōu)化建議:因?yàn)槊看螆?zhí)行一次包含非常量的字符串相加時(shí),都進(jìn)行了一次 StringBuilder對象的創(chuàng)建,所以如果需要多次連接,可以直接創(chuàng)建 StringBuilder對象,使用一個(gè) StringBuilder對象進(jìn)行字符串拼接,避免創(chuàng)建多個(gè)對象降低效率。
對象創(chuàng)建數(shù)量
對象,包括 new的對象以及字符串對象。
1、對于String ss = new String ("ab"),這個(gè)過程首先會(huì)在會(huì)在字符串常量池中創(chuàng)建一個(gè) "ab"字符串常量,然后再在堆上創(chuàng)建一個(gè) new String()的對象,在這個(gè)對象中會(huì)保存常量池中 "ab"的地址信息,最后在棧上創(chuàng)建一個(gè)局部變量 ss ,保存堆中創(chuàng)建的對象地址。所以全程創(chuàng)建了堆中的一個(gè)對象和字符串常量池中的一個(gè)對象。
2、new String("a") + new String("b")。嚴(yán)格來看,創(chuàng)建了六個(gè)對象。
首先new String("a")和 new String("b") ,分為創(chuàng)建了兩個(gè)對象。兩者相加時(shí),會(huì)創(chuàng)建一個(gè) StringBuilder對象,而在 StringBuilder.toString()方法中,也會(huì)創(chuàng)建一個(gè) String對象
3、String s1 = "a", String s2 = "b", String s3 = "a" + "b" + s1 + "c" + s2;對應(yīng)的字節(jié)碼如下:
字符串常量池中會(huì)有四個(gè)字符串對象,分別是 "a"、"b"、"ab"、"c"。在開始因?yàn)?s1、s2的賦值,會(huì)將 "a"、"b"分別加入字符串常量池,然后執(zhí)行第三步,運(yùn)行順序是從左到右,首先執(zhí)行 "a" + "b" ,因?yàn)閮蓚€(gè)都是常量,所以會(huì)因?yàn)榫幾g器的優(yōu)化直接返回 "ab",并且因?yàn)橛?jì)算的兩個(gè)參數(shù)都是常量,所以直接加入字符串常量池,隨后因?yàn)榕c變量 s1相加,所以調(diào)用 StringBuilder的append方法,得到的結(jié)果保存到局部變量表中,所以引入常量 "c",因?yàn)槭浅A?,所以還是會(huì)引入字符串常量池,然后與前面拼接得到的結(jié)果再次拼接,最后再與變量 s2相加,因?yàn)椴皇浅A克赃€是不會(huì)將結(jié)果加入字符串常量池。
除此之外,還需要注意,上面三種情況是在初始情況下,也就是字符串常量池中沒有要加入的字符串時(shí)的場景,如果字符串常量池中預(yù)先就包含要加入的字符串,那么就會(huì)直接將常量池中的對應(yīng)的字符串地址返回給調(diào)用方。比如 String s1 = "a",在常量池中沒有 "a"時(shí),創(chuàng)建的對象是 1個(gè),而如果常量池中已經(jīng)存在,那么就會(huì)將其地址直接返回賦給 s1。那么創(chuàng)建的對象就是 0個(gè)了。
intern()與字符串相等判斷
intern()方法是 String類的一個(gè)native方法,作用是嘗試將調(diào)用這個(gè)方法的字符串對象加入字符串常量池中,然后返回常量池中存儲的值。在開頭說過,在 JDK7開始字符串常量池可以存儲字符串引用,導(dǎo)致字符串操作的過程可能會(huì)之前不一樣,從而得到不同的結(jié)果。
intern()方法的執(zhí)行:
1.6及之前:嘗試將當(dāng)前字符串常量加入常量池,如果常量池存在就返回地址值;如果不存在就先加入常量池,然后再返回加入位置的地址值。
1.7開始:嘗試將當(dāng)前字符串常量加入常量池,如果存在就將返回地址值;如果不存在就存入當(dāng)前 String 字符串的地址值。
下面以一個(gè)例子來解釋一下,在JDK7和JDK7之前下面代碼執(zhí)行分別是什么結(jié)果。
@Test public void test1(){ String s = new String("1"); s.intern(); String s2 = "1"; System.out.println(s == s2); String s3 = new String("1") + new String("1"); s3.intern(); String s4 = "11"; System.out.println(s3 == s4); }
先說結(jié)論:
JDK7之前: false、false。
JDK7及之后:false、true。
原因:
1、首先先看上面 3------6行的,首先,第三行會(huì)在字符串常量池中添加 "1" ,然后在堆中創(chuàng)建一個(gè)對象,保存 "1"在常量池中的地址,再在局部變量表中添加一個(gè) s保存堆中對象的地址。隨后執(zhí)行第四行,此時(shí) s指向的字符串已經(jīng)在常量池中了,所以這一步無效,第五行因?yàn)槌A砍匾呀?jīng)存在 "1" ,所以 JDK7或之前執(zhí)行的邏輯是一樣的,直接將 "1"在常量池中的地址返回給 s2。然后判斷,s指向的是堆中的對象,而 s2指向的是常量池中的字符串常量,所以無論是 JDK7還是之前的都是 false。
2、然后再看下面 9-----12行。因?yàn)榍懊嬉呀?jīng)在常量池中添加 "1",所以第9行會(huì)直接返回地址,然后執(zhí)行添加操作,創(chuàng)建字符串 "11",此時(shí)并沒有添加到常量池,然后執(zhí)行第10行,因?yàn)槌A砍夭淮嬖?"11",所以 JDK7之前直接加入常量池,JDK7及以后則直接將 "11"的地址存入常量池,而 s3則不變,還是保存的是常量池外的那個(gè) "11"的地址值。然后執(zhí)行 11行,因?yàn)槌A砍匾汛嬖?"11",所以 s4就是返回 "11"的地址值,不同的是在 JDK7之前因?yàn)槌A砍乇4娴氖?"11"常量,所以返回的是常量池中的地址值;而 JDK7 及以后常量池保存的是常量池外的 "11"的地址值,所以返回的是池外的地址值。所以最后判斷在 JDK7之前是 false,而在 JDK7開始是 true。
到此這篇關(guān)于JVM 中的StringTable的文章就介紹到這了,更多相關(guān)JVM 中的StringTable內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決springboot的JPA在Mysql8新增記錄失敗的問題
這篇文章主要介紹了解決springboot的JPA在Mysql8新增記錄失敗的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06SpringCloud應(yīng)用idea實(shí)現(xiàn)可相互調(diào)用的多模塊程序詳解
IDEA 全稱 IntelliJ IDEA,是java編程語言的集成開發(fā)環(huán)境。IntelliJ在業(yè)界被公認(rèn)為最好的Java開發(fā)工具,尤其在智能代碼助手、代碼自動(dòng)提示、重構(gòu)、JavaEE支持、各類版本工具(git、svn等)、JUnit、CVS整合、代碼分析、 創(chuàng)新的GUI設(shè)計(jì)等方面的功能可以說是超常的2022-07-07SpringBoot中Filter?bean如何添加到Servlet容器
這篇文章主要介紹了SpringBoot中Filter bean是怎么被添加到Servlet容器中的,本文分步驟結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08spring boot教程之產(chǎn)生的背景及其優(yōu)勢
這篇文章主要介紹了spring boot教程之產(chǎn)生的背景及其優(yōu)勢的相關(guān)資料,需要的朋友可以參考下2022-08-08SpringMVC架構(gòu)的項(xiàng)目 js,css等靜態(tài)文件導(dǎo)入有問題的解決方法
下面小編就為大家?guī)硪黄猄pringMVC架構(gòu)的項(xiàng)目 js,css等靜態(tài)文件導(dǎo)入有問題的解決方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10如何基于java向mysql數(shù)據(jù)庫中存取圖片
這篇文章主要介紹了如何基于java向mysql數(shù)據(jù)庫中存取圖片,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02關(guān)于IDEA 2020.3 多窗口視圖丟失的問題
這篇文章主要介紹了關(guān)于IDEA 2020.3 多窗口視圖丟失的問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12