《Java面試題系列》:一個長知識又很有意思的專欄,深入挖掘、分析原始碼、匯總原理、圖文結合,打造公眾號系列文章,面試與否均可提升Level,歡迎持續關注【程式新視界】,本篇為第5篇,
【番外篇】本篇核心:JDK各個版本中JDK的運行時常量池、字串常量池、靜態常量池的功能及存盤位置,
在寫本系列文章時,發現一旦追究起底層實作都會涉及到一些記憶體結構的問題,其中涉及比較多的便是常量池,本篇文章匯總一下JDK的運行時常量池、字串常量池、靜態常量池的功能及存盤結構,
JVM運行時記憶體結構
在了解常量池之前我們先通過一張圖了解一下JVM的整個記憶體分布圖,下圖為JDK7的記憶體結構:

在上圖中JVM所管理的記憶體主要包括以下區域:程式計數器(Program Counter Register)、虛擬機堆疊(VM Stack)、本地方法堆疊(Native Method Stack)、方法區(Method Area)、堆(Heap),
不同版本的JVM的記憶體結構有不同的變化,這些變化對我們今天要講的三個概念會有所影響,后面我們會逐一講解,
了解了JVM記憶體結構,那么運行時常量池、字串常量池、靜態常量池對于的都位于JVM的什么區域呢?先來看看它們的定義及功能,
靜態常量池
Java程式要運行時,需要編譯器先將源代碼檔案編譯成位元組碼(.class)檔案,然后在由JVM解釋執行,
class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(Constant pool table),用于存放編譯期生成的各種字面量和符號參考,這部分內容將在類加載后進入運行時常量池中存放,
靜態常量池就是上面說的class檔案中的常量池,class常量池是在編譯時每個class檔案中都存在,不同的符號資訊放置在不同標志的常量表中,

常量池中存放的符號資訊,在JVM執行指令時需要依賴使用,常量池中的所有項都具有如下通用格式:
cp_info {
u1 tag; //表示cp_info的單位元組標記位
u1 info[]; //兩個或更多的位元組表示這個常量的資訊,資訊格式由tag的值確定
}
支持的型別資訊如下:

以CONSTANT_Class為例,它用于表示類或者介面,格式如下:
CONSTANT_Class_info {
u1 tag; //這個值為CONSTANT_Class (7)
u2 name_index;//一個index,表示一個索引,參考的是CONSTANT_UTF8_info
}
CONSTANT_Class_info型別是由一個tag和一個name_index組成,name_index中的index表示它是一個索引,參考的是CONSTANT_UTF8_info,
CONSTANT_Utf8_info用于表示字符常量的值,結構如下所示:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
tag表示為:CONSTANT_Utf8(1);length指明了bytes[]陣列的長度;bytes[]陣列參考了上一個length作為其長度,字符常量采用改進過的UTF-8編碼表示,
對于靜態常量池我們需要知道它存在于編譯器,如果說與運行時有關的話,可以說運行時中的常量是JVM加載class檔案之后進行分配的,
運行時常量池
運行時常量池就是將編譯后的類資訊放入方法區中,也就是說它是方法區的一部分,
運行時常量池用來動態獲取類資訊,包括:class檔案元資訊描述、編譯后的代碼資料、參考型別資料、類檔案常量池等,
運行時常量池是在類加載完成之后,將每個class常量池中的符號參考值轉存到運行時常量池中,每個class都有一個運行時常量池,類在決議之后將符號參考替換成直接參考,與全域常量池中的參考值保持一致,
運行時常量池相對于class檔案常量池的另外一個特性是具備動態性,java語言并不要求常量一定只有編譯器才產生,也就是并非預置入class檔案中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,
字串常量池
字串池里的內容是在類加載完成,經過驗證、準備階段之后存放在字串常量池中,關于字串常量池的具體實作我們這里先不展開,后面用專門的文章來進行講解,
字串常量池的處理機制我們前面文章已經講到,只會存盤一份,被所有的類共享,基本流程是:創建字串之前檢查常量池中是否存在,如果存在則獲取其參考,如果不存在則創建并存入,回傳新物件參考,
字串常量池隨著JDK版本的演化所在的位置也在不斷的變化,下面我們會專門用圖講解一下,
常量池記憶體位置演化
在JDK1.7之前運行時常量池邏輯包含字串常量池存放在方法區, 此時hotspot虛擬機對方法區的實作為永久代,

在JDK1.7字串常量池和靜態變數被從方法區拿到了堆中,運行時常量池剩下的還在方法區, 也就是hotspot中的永久代,

在JDK8 hotspot移除了永久代用元空間(Metaspace)取而代之, 這時候字串常量池還在堆,運行時常量池還在方法區,只不過方法區的實作從永久代變成了元空間(Metaspace)

通過上面的圖解,我們可以輕易得知在不同的版本中方法區及內部組成部分是在不斷變化的,
小結
通過本篇文章我們針對JDK的運行時常量池、字串常量池、靜態常量池進行逐一講解,同時針對不同版本的JVM(hotspot虛擬機)中它們所處的記憶體位置進行了圖解,
總結一下就是:靜態變數處于編譯器,存在于class檔案內,可通過javap verbose命令查看字串合并時查看的是靜態常量池里面的內容;字串常量池曾經屬于運行時常量池的一部分,位于方法區,但隨著JVM版本的演變,二者已經分開,在JDK8以后字串常量池位于堆中,而運行時常量池位于方法區,
其實關于此部分的內容還有很多,特別是字串常量池,歡迎大家持續關注,后續會對字串常量池再進行針對性的分析,在此程序中也發現很多文章在沒有指定JDK版本的情況下進行描述,都有失偏駁,本系列為大家考證,去偽存真,
參考文章:
https://blog.csdn.net/qq_31615049/article/details/81611918
https://blog.csdn.net/weixin_43232955/article/details/107411378

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/36341.html
標籤:Java
