JVM詳解之:執行時常量池
阿新 • • 發佈:2020-07-16
[toc]
# 簡介
JVM在執行的時候會對class檔案進行載入,連結和初始化的過程。class檔案中定義的常量池在JVM載入之後會發生什麼神奇的變化呢?快來看一看吧。
# class檔案中的常量池
之前我們在講class檔案的結構時,提到了每個class檔案都有一個常量池,常量池中存了些什麼東西呢?
字串常量,類和介面名字,欄位名,和其他一些在class中引用的常量。
# 執行時常量池
但是隻有class檔案中的常量池肯定是不夠的,因為我們需要在JVM中執行起來。
這時候就需要一個執行時常量池,為JVM的執行服務。
執行時常量池和class檔案的常量池是一一對應的,它就是class檔案的常量池來構建的。
執行時常量池中有兩種型別,分別是symbolic references符號引用和static constants靜態常量。
其中靜態常量不需要後續解析,而符號引用需要進一步進行解析處理。
什麼是靜態常量,什麼是符號引用呢? 我們舉個直觀的例子。
~~~java
String site="www.flydean.com"
~~~
上面的字串"www.flydean.com"可以看做是一個靜態常量,因為它是不會變化的,是什麼樣的就展示什麼樣的。
而上面的字串的名字“site”就是符號引用,需要在執行期間進行解析,為什麼呢?
因為site的值是可以變化的,我們不能在第一時間確定其真正的值,需要在動態執行中進行解析。
## 靜態常量詳解
執行時常量池中的靜態常量是從class檔案中的constant_pool構建的。可以分為兩部分:String常量和數字常量。
### String常量
String常量是對String物件的引用,是從class中的CONSTANT_String_info結構體構建的:
~~~java
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
~~~
tag就是結構體的標記,string_index是string在class常量池的index。
string_index對應的class常量池的內容是一個CONSTANT_Utf8_info結構體。
~~~java
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
~~~
CONSTANT_Utf8_info是啥呢?它就是要建立的String物件的變種UTF-8編碼。
我們知道unicode的範圍是從0x0000 至 0x10FFFF。
變種UTF-8就是將unicode進行編碼的方式。那是怎麼編碼呢?
![](https://img-blog.csdnimg.cn/20200616183643720.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_35,color_8F8F8F,t_70)
從上圖可以看到,不同的unicode範圍使用的是不同的編碼方式。
> 注意,如果一個字元佔用多個位元組,那麼在class檔案中使用的是 big-endian 大端優先的排列方式。
> 如果字元範圍在FFFF之後,那麼使用的是2個3位元組的格式的組合。
講完class檔案中CONSTANT_String_info的結構之後,我們再來看看從CONSTANT_String_info建立執行時String常量的規則:
1. 規則一:如果String.intern之前被呼叫過,並且返回的結果和CONSTANT_String_info中儲存的編碼是一致的話,表示他們指向的是同一個String的例項。
2. 規則二:如果不同的話,那麼會建立一個新的String例項,並將執行時String常量指向該String的例項。最後會在這個String例項上呼叫String的intern方法。呼叫intern方法主要是將這個String例項加入字串常量池。
### 數字常量
數字常量是從class檔案中的CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info和 CONSTANT_Double_info 構建的。
## 符號引用詳解
符號引用也是從class中的constant_pool中構建的。
對class和interface的符號引用來自於CONSTANT_Class_info。
對class和interface中欄位的引用來自於CONSTANT_Fieldref_info。
class中方法的引用來自於CONSTANT_Methodref_info。
interface中方法的引用來自於CONSTANT_InterfaceMethodref_info。
對方法控制代碼的引用來自於CONSTANT_MethodHandle_info。
對方法型別的引用來自於CONSTANT_MethodType_info。
對動態計算常量的符號引用來自於CONSTANT_MethodType_info。
對動態計算的call site的引用來自於CONSTANT_InvokeDynamic_info。
# String Pool字串常量池
我們在講到執行時常量池的時候,有提到String常量是對String物件的引用。那麼這些建立的String物件是放在什麼地方呢?
沒錯,就是String Pool字串常量池。
這個String Pool在每個JVM中都只會維護一份。是所有的類共享的。
String Pool是在1.6之前是存放在方法區的。在1.8之後被放到了java heap中。
> 注意,String Pool中存放的是字串的例項,也就是用雙引號引起來的字串。
那麼問題來了?
~~~java
String name = new String("www.flydean.com");
~~~
到底建立了多少個物件呢?
# 總結
class檔案中常量池儲存的是字串常量,類和介面名字,欄位名,和其他一些在class中引用的常量。每個class都有一份。
執行時常量池儲存的是從class檔案常量池構建的靜態常量引用和符號引用。每個class都有一份。
字串常量池儲存的是“字元”的例項,供執行時常量池引用。
執行時常量池是和class或者interface一一對應的,那麼如果一個class生成了兩個例項物件,這兩個例項物件是共享一個執行時常量池還是分別生成兩個不同的常量池呢?歡迎小夥伴們留言討論。
> 本文連結:[http://www.flydean.com/jvm-run-time-constant-pool/](http://www.flydean.com/jvm-run-time-constant-pool/)
>
> 最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
>
> 歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!
![](https://img-blog.csdnimg.cn/20200709152618916.png)