詳解JVM 中的StringTable

2022-10-06 02:24:09 字數 3226 閱讀 9672

字串常量池是 jvm中的乙個重要結構,用於儲存jvm執行時產生的字串。在jdk7之前在方法區中,儲存的是字串常量。而字串常量池在 jdk7開始移入堆中,隨之而來的是除了儲存字串常量外,還可以儲存字串引用(因為在堆中,引用堆中的字串常量很方便,所以可以儲存引用)。這使得很多字串的操作在 jdk7中和在之前的版本中執行是不同的結果。這也是為什麼字串相關的問題是如此具有迷惑性的原因之一。

string:在 jdk9之前,string底層是使用 char陣列來儲存字串資料的,而在 jdk9開始,使用 byte陣列+編碼來代替 char陣列,這是為了節省空間,因為不同編碼的資料佔空間不一樣,很多單位資料www.cppcns.com只需要乙個 byte(8位元組) 就可以儲存,而使用 位元組)就會浪費多餘的空間。

字串常量池:底層使用 hashtable來儲存字串,在 jdk6 hashtable的陣列長度是1006,jdk7開始變成了 60013,這是為了避免儲存字串過多導致鍊錶長度過長從而查詢效率降低。可以使用引數 -xx:stringtablesize=來設定 stringtable陣列的長度。

1、對於字串常量相加,編譯器會優化成直接相加。

如 string ss = "a" + "b",在編譯器的優化下,實際上只會建立乙個 "ab" 字串。

而 fin程式設計客棧al string s1 = "a"; string s2 = s1+"b",除了建立字串 "a"外,只會建立 "ab"。

操作相關字串如下:

可以看到只對字串 "a"、"ab"進行了入池操作(ldc)

2、對於包含字串變數的相加,不會在字串常量池中建立對應的字串。

如 string s1 = "a"; string s2 = s1 + "b",執行完後字串常量池中只會包含 "a"、"b" 字串。

對於 s1 + "b",下面是其位元組碼操作

可以看到,相加操作實際上是呼叫 stringbuilder的append方法進行字串拼接,然後呼叫它的 tostring方法獲取返回值儲存輸出,期間並沒有入池操作(ldc)。

由此得出的優化www.cppcns.com建議:因為每次執行一次包含非常量的字串相加時,都進行了一次 stringbuilder物件的建立,所以如果需要多次連線,可以直接建立 stringbuilder物件,使用乙個 stringbuilder物件進行字串拼接,避免建立多個物件降低效率。

物件,包括 new的物件以及字串物件。

1、對於string ss = new string ("ab"),這個過程首先會在會在字串常量池中建立乙個 "ab"字串常量,然後再在堆上建立乙個 new string()的物件,在這個物件中會儲存常量池中 "ab"的位址資訊,最後在棧上建立乙個區域性變數 ss ,儲存堆中建立的物件位址。所以全程建立了堆中的乙個物件和字串常量池中的乙個物件。

2、new string("a") + new string("b")。嚴格來看,建立了六個物件。

首先new string("a")和 new string("b") ,分為建立了兩個物件。兩者相加時,會建立乙個 stringbuilder物件,而在 stringbuilder.tostring()方法中,也會建立乙個 string物件

3、string s1 = "a", string s2 = "b", string s3 = "a" + "b" + s1 + "c" + s2;對應的位元組碼如下:

字串常量池中會有四個字串物件,分別是 "a"、"b"、"ab"、"c"。在開始因為 s1、s2的賦值,會將 "a"、"b"分別加入字串常量池,然後執行第三步,執行順序是從左到右,首先執行 "a" + "b" ,因為兩個都是常量,所以會因為編譯器的程式設計客棧優化直接返回 "ab",並且因為計算的兩個引數都是常量,所以直接加入字串常量池,隨後因為與變數 s1相加,所以呼叫 stringbuilder的append方法,得到的結果儲存到區域性變數表中,所以引入常量 "c",因為是常量,所以還是會引入字串常量池,然後與前面拼接得到的結果再次拼接,最後再與變數 s2相加,因為不是常量所以還是不會將結果加入字串常量池。

除此之外,還需要注意,上面三種情況是在初始情況下,也就是字串常量池中沒有要加入的字串時的場景,如果字串常量池中預先就包含要加入的字串,那麼就會直接將常量池中的對應的字串位址返回給呼叫方。比如 string s1 = "a",在常量池中沒有 "a"時,建立的物件是 1個,而如果常量池中已經存在,那麼就會將其位址直接返回賦給 s1。那麼建立的物件就是 0個了。

intern()方法是 string類的乙個native方法,作用是嘗試將呼叫這個方法的字串物件加入字串常量池中,然後返回常量池中儲存的值。在開頭說過,在 jdk7開始字串常量池可以儲存字串引用,導致字串操作的過程可能會之前不一樣,從而得到不同的結果。

intern()方法的執行:

1.6及之前:嘗試將當前字串常量加入常量池,如果常量池存在就返回位址值;如果不存在就先加入常量池,然後再返回加入位置的位址值。

1.7開始:嘗試將當前字串常量加入常量池,如果存在就將返回位址值;如果不存在就存入當前 string 字串的位址值。

下面以乙個例子來解釋一下,在jdk7和jdk7之前下面**執行分別是什麼結果。

@test

public void test1()

先說結論:

jdk7之前: false、false。

jdk7及之後:false、true。

原因:1、首先先看上面 3------6行的,首先,第三行會在字串常量池中新增 "1" ,然後在堆中建立乙個物件,儲存 "1"在常量池中的位址,再在區域性變數表中新增乙個 s儲存堆中物件的位址。隨後執行第四行,此時 s指向的字串已經在常量池中了,所以這一步無效,第五行因為常量池已經存在 "1" ,所以 jdk7或之前執行的邏輯是一樣的,直接將 "1"在常量池中的位址返回給 s2。然後判斷,s指向的是堆中的物件,而 s2指向的是常量池中的字串常量,所以無論是 jdk7還是之前的都是 false。

2、然後再看下面 9-----12行。因為前面已經在常量池中新增 "1",所以第9行會直接返回位址,然後執行新增操作,建立字串 "11",此時並沒有新增到常量池,然後執行第10行,因為常量池不存在 "11",所以 jdk7之前直接加入常量池,jdk7及以後則直接將 "11"的位址存入常量池,而 s3則不變,還是儲存的是常量池外的那個 "11"的位址值。然後執行 11行,因為常量池已存在 "11",所以 s4就是返回 "11"的位址值,不同的是在 jdk7之前因為常量池儲存的是 "11"常量,所以返回的是常量池中的位址值;而 jdk7 及以後常量池儲存的是常量池外的 "11"的位址值,所以返回的是池外的位址值。所以最後判斷在 jdk7之前是 false,而在 jdk7開始是 true。

關於JVM引數詳解

1.標準引數 所有的jvm實現都必須實現這些引數的功能,而且向後相容 2.非標準引數 x 預設jvm實現這些引數的功能,但是並不保證所有jvm實現都滿足,且不保證向後相容 3.非stable引數 xx 此類引數各個jvm實現會有所不同,將來可能會隨時取消,需要慎重使用 但是,這些引數往往是非常有用的...

JVM堆記憶體詳解

j a堆記憶體管理是影響效能主要因素之一。堆記憶體溢位是j a專案非常常見的故障,在解決該問題之前,必須先了解下j a堆記憶體是怎麼工作的。先看下j a堆記憶體是如何劃分的,如圖 jvm記憶體劃分為堆記憶體和非堆記憶體,堆記憶體分為年輕代 young generation 老年代 old gener...

詳解JVM的分代模型

前言 本篇文章我們將針對jvm堆記憶體的分代模型做乙個詳細的解析,和大家一起輕鬆理解jvm的分代模型。相信看過其他文章的小夥伴們可能都知道,jvm的分代模型包括 年輕代 老年代 永久代。那麼它們分別代表著什麼角色呢?我們先來看一段 public class main public static vo...