《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