1.字元串在記憶體裡面到底是怎麼樣存在的?
以string類型的對象在堆區申請到一塊記憶體區,一共24個位元組,其中mark_word占8個位元組,_klass指針4個位元組,value4個位元組,hash4個位元組,coder1個位元組,對齊3個位元組,value指向存放真正字元的byte[]數組對象。當然這個大小跟hotspot是32位還是64位有關,以及64位情況,是否開啟指針壓縮有關。
-xx:+usecompressedoops:針對oop指針壓縮,-xx:+usecompressedclasspointers:針對klass指針壓縮
以上24個位元組是針對64位預設,也就是開啟oop指針壓縮和klass指針壓縮的情況下。
2.string的 intern 方法到底幹了什麼?
如果常量池中已經有了這個字元串,那麼直接傳回常量池中它的引用,如果沒有,那就将它的引用儲存一份到字元串常量池,然後直接傳回這個引用。
3.s1.intern(); 和 s1 = s1.intern();一樣嗎?
不一樣。
s1.intern(); 将s1的引用儲存一份到字元串常量池。如果常量池中已經有了這個字元串,相對于什麼都沒做。
s1 = s1.intern(); 将s1的引用儲存一份到字元串常量池,如果常量池中已經有了這個字元串,傳回常量池中的引用指派給s1,那麼也就是說s1的引用變了,如果常量池中沒有這個字元串,那就将它的引用儲存一份到字元串常量池。
差別在于:s1=s1.intern(); s1的引用值可能會發生變化,但是s1.intern(); 對s1的引用值不産生任何改變。
4.string s = “hello”;和string s = new string(“hello”);還有string s = “he”+”llo”;以及string s = new string(“he”)+new string(“llo”);的差別是什麼?
(1).string s = “hello”;
s指向堆區的一個string對象,并且該對象引用儲存在字元串常量池裡面。
(2).string s = new string(“hello”);
s指向堆區的一個string對象,并且該對象引用沒有儲存在字元串常量池裡面。執行s=s.intern(); 後,s的引用值改變,指向字元串常量池存在的引用值。
(3).string s = “he”+”llo”;
和string s = “hello”;一樣,在編譯階段就會合并為一個字元串。是以跟(1)是一樣的。
(4).string s = new string(“he”)+new string(“llo”);
這個要稍微複雜一點,涉及到方法調用,在jdk9以下,采用stringbuilder.append拼接,傳回string,在jdk9及以上,采用stringconcatfactory.makeconcatwithconstants拼接,傳回string。并且該對象引用沒有儲存在字元串常量池裡面。執行s=s.intern(); 後,s的引用值改變,指向字元串常量池存在的引用值。
5.字元串哪些情況會進入字元串常量池?以及什麼時候進入字元串常量池?
隻要是””包裹的字元串,都會進入字元串常量池,以及拼接後調用intern方法的字元串。那可能就人會問了,那string s = new string(“hello”);和string s = new string(“he”)+new string(“llo”);這裡為什麼沒有進入字元串常量池,其實,那是因為string s = new string(“hello”);這裡産生了兩個對象,一個是”hello”包裹的匿名對象,它的引用位址會進入字元串常量池,另外一個是new出來的對象,也就是s最終指向的引用對象,隻不過new出來的對象和”hello”包裹的匿名對象指向了相同的byte[]數組對象。而string s = new string(“he”)+new string(“llo”);涉及到5個對象,一個是”he”包裹的匿名對象,它的引用位址會進入字元串常量池,還有一個是”llo”包裹的匿名對象,它的引用位址會進入字元串常量池,另外就是兩個new出來的string對象,new出來的兩個string對象拼接之後,會再生成1個新對象傳回給s,這3個對象都沒有進入字元串常量池,如果調用s.intern(),則新生成的拼接string對象會進入字元串常量池。
如果是””包裹的字元串,在類加載的時候,有一個解析階段,其實在這個時候就進入了字元串常量池,但是對于constant_string類型在hotspot vm中的延遲執行的,真正執行是要到首次執行ldc指令的時候,才會真正的進入字元串常量池。而調用string的 intern 方法,方法執行的時候,就會進入字元串常量池。
6.字元串常量池是什麼?在記憶體裡面到底是怎麼樣存在的?和字元串到底是什麼關系?
字元串常量池是一個存儲java.lang.string執行個體引用的hashmap,key就是對應對應字元串數組,value就是java.lang.string執行個體引用。存在方式見string在記憶體裡面的結構圖所示。
7.字元串常量池和運作時常量池,常量池是什麼關系?它們分别是什麼?
常量池/class檔案常量池/靜态常量池:存在于class位元組碼檔案中的一段二進制資料,存儲在磁盤或網絡中,未加載到記憶體。
運作時常量池:就是class檔案中的常量池,在運作的過程中,加載到記憶體後,在方法區的一塊記憶體資料,對應jvm的symboltable資料結構存儲,存在于元空間。
字元串常量池:字元串的配置設定,和其他的對象配置設定一樣,耗費高昂的時間與空間代價,作為最基礎的資料類型,大量頻繁的建立字元串,極大程度地影響程式的性能。jvm為了提高性能和減少記憶體開銷,在執行個體化字元串常量的時候進行了一些優化:為字元串開辟一個字元串常量池,類似于緩存;建立字元串常量時,首先堅持字元串常量池是否存在該字元串;存在該字元串,傳回引用執行個體,不存在,執行個體化該字元串并放入池中。對應jvm的stringtable資料結構存儲,stringtable本體存在元空間,實際引用對象存在于堆區。
整型常量池/包裝類型常量:此種常量池并非jvm層面,而是java層面的封裝,比如integercache(integer的内部類),實作了integer的常量池,其它還有byte, short, integer, long, character,boolean。
常量池是為了避免頻繁的建立和銷毀對象而影響系統性能,其實作了對象的共享。例如字元串常量池,在編譯階段就把所有的字元串文字放到一個常量池中。
節省記憶體空間:常量池中所有相同的字元串常量被合并,隻占用一個空間。
節省運作時間:比較字元串時,==比equals()快。對于兩個引用變量,隻用==判斷引用是否相等,也就可以判斷實際值是否相等。
constantpoolcache主要用于存儲某些位元組碼指令所需的解析(resolve)好的常量項,例如給[get|put]static、[get|put]field、invoke[static|special|virtual|interface|dynamic]等指令對應的常量池項用。
每個instanceklass關聯着一個constantpool,作為該類型的運作時常量池。這個常量池的結構跟class檔案裡的常量池是一一對應的。constant pool 是一個全局公共數組,每加載一個類,這個類中繼資料的指針就會加入這個數組,指針指向的實際的資料,例如字面量,符号常量等等,都會放入全局公共 symbol table 以及 全局公共 stringtable。