Java中幾種常量池比較
目錄
Java中主要有三種常量池,分別是class常量池、字串常量池和執行時常量池。我們對這三個常量池進行對比。
class常量池
我們寫的每一個Java類被編譯之後都會生成一個對應的Class檔案。Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯期生成的各種字面量和符號引用。每個類都有一個Class常量池。
什麼是字面量和符號引用
字面量比較接近於Java語言層面的常量概念,如文字字串、宣告為final的常量值和八種基本型別的變數等。而符號引用則屬於編譯原理方面的概念,包括了下面三類變數:
- 類和介面的全限定名
- 欄位的名稱和描述符
- 方法的名稱和描述符
Java程式碼在進行Javac編譯的時候是在虛擬機器載入Class檔案的時候進行動態連線。也就是說,在Class檔案中不會儲存各個方法、欄位的最終記憶體佈局資訊,因此這些欄位、方法的符號引用不經過執行期轉換的話無法得到真正的記憶體入口地址,也就無法直接被虛擬機器使用。當虛擬機器執行時,需要從常量池獲得對應的符號引用,再在類建立時或者執行時解析、翻譯到具體的記憶體地址中。
常量池中每一項常量都是一個表,在JDK1.7之前共有11種結構各不相同的表結構資料,在JDK1.7中為了更好地支援動態語言呼叫,又額外增加了3種。
字串常量池
字串常量池的設計思想
和其他物件分配一樣,字串的分配耗費高昂的時間和空間代價,作為最基礎的資料型別大量頻繁建立字串會極大的影響效能。
JVM為了提高效能和減少記憶體開銷,在例項化字串常量的時候進行了一些優化
-
為字串開闢一個字串常量池,類似於快取區
-
建立字串常量時,首先檢查字串常量池是否存在該字串
-
若存在該字串,返回引用例項;若不存在,例項化該字串並放入池中
實現的基礎
-
實現該優化的基礎是因為字串是不可變的,可以不用擔心資料衝突進行共享
-
執行時例項建立的全域性字串常量池中有一個表,總是為池中每個唯一的字串物件維護一個引用,這就意味著它們一直引用著字串常量池中的物件,所以,在常量池中的這些字串不會被垃圾收集器回收
字串常量池的位置
在JDK1.6之前,字串常量池在方法區,在JDK1.7,字串常量池被移入了堆中。移入堆中的原因大概是方法區的記憶體空間太小了。
字串常量池內部結構
- 在HotSpot VM裡實現的string pool功能的是一個StringTable類,它是一個Hash表,預設值大小長度是1009;這個StringTable在每個HotSpot VM的例項只有一份,被所有的類共享。字串常量由一個一個字元組成,放在了StringTable上。
- 在JDK1.6中,StringTable的長度是固定的,長度就是1009,因此如果放入String Pool中的String非常多,就會造成hash衝突,導致連結串列過長,當呼叫String#intern()時會需要到連結串列上一個一個找,從而導致效能大幅度下降;
- 在JDK1.7中,StringTable的長度可以通過引數指定:
-XX:StringTableSize=66666
字串常量池裡放的是什麼?
- 在JDK1.6及之前版本中,String Pool裡放的都是字串常量;
- 在JDK1.7中,由於String#intern()發生了改變,因此String Pool中也可以存放放於堆內的字串物件的引用。
需要說明的是:字串常量池中的字串只存在一份!
如:
String s1 = "hello,world!";
String s2 = "hello,world!";
即執行完第一行程式碼後,常量池中已存在 “hello,world!”,那麼 s2不會在常量池中申請新的空間,而是直接把已存在的字串記憶體地址返回給s2。
執行時常量池
- 執行時常量池存在於記憶體中的方法區,class常量池被載入到記憶體之後會存放在執行時常量池中。除了儲存Class檔案中描述的符號引用外,還會把符號引用翻譯為直接引用,並存儲在執行時常量池中。
- 執行時常量池相對於Class檔案常量來說另一個特徵是它是動態的。執行時可以將新的常量放入池中,比如(String#intern())
- JVM在執行某個類的時候,必須經過載入、連線、初始化,而連線又包括驗證、準備、解析三個階段。而當類載入到記憶體中後,jvm就會將class常量池中的內容存放到執行時常量池中,由此可知,執行時常量池也是每個類都有一個。在解析階段,會把符號引用替換為直接引用,解析的過程會去查詢字串常量池,也就是我們上面所說的StringTable,以保證執行時常量池所引用的字串與字串常量池中是一致的。
總結
存放位置 | 靜態還是動態 | 存放的內容 | |
Class常量池 | Class檔案中 | 靜態 | 編譯期生成的各種字面量和符號引用 |
字串常量池 | 方法區 | 靜態 |
JDK1.6及之前,存放的都是字串常量 JDK1.7之後,還可以存放對字串物件的引用 |
執行時常量池 | 方法區 | 動態 | class常量池在類載入之後存放在其中,還有動態放入的新的常量(比如String#intern()) |
參考資料:
《深入理解Java虛擬機器》周志明 著