在Java中,常量池的概念想必不少人都據說過。這也是面試中比較常考的題目之一。在Java有關的面試題中,通常習慣經過String的有關問題來考察面試者對於常量池的知識的理解,幾道簡單的String面試題難倒了無數的開發者。因此說,常量池是Java體系中一個很是重要的概念。java
談到常量池,在Java體系中,共用三種常量池。分別是字符串常量池、Class常量池和運行時常量池。程序員
本文是《好好說說Java中的常量池》系列的第一篇,先來介紹一下到底什麼是Class常量池。面試
在Java代碼的編譯與反編譯那些事兒中咱們介紹過Java的編譯和反編譯的概念。咱們知道,計算機只認識0和1,因此程序員寫的代碼都須要通過編譯成0和1構成的二進制格式纔可以讓計算機運行。編程
咱們在《深刻分析Java的編譯原理》中提到過,爲了讓Java語言具備良好的跨平臺能力,Java獨具匠心的提供了一種能夠在全部平臺上都能使用的一種中間代碼——字節碼(ByteCode)。vim
有了字節碼,不管是哪一種平臺(如Windows、Linux等),只要安裝了虛擬機,均可以直接運行字節碼。數組
一樣,有了字節碼,也解除了Java虛擬機和Java語言之間的耦合。這話可能不少人不理解,Java虛擬機不就是運行Java語言的麼?這種解耦指的是什麼?編程語言
其實,目前Java虛擬機已經能夠支持不少除Java語言之外的語言了,如Groovy、JRuby、Jython、Scala等。之因此能夠支持,就是由於這些語言也能夠被編譯成字節碼。而虛擬機並不關心字節碼是有哪一種語言編譯而來的。工具
Java語言中負責編譯出字節碼的編譯器是一個命令是javac
。佈局
javac是收錄於JDK中的Java語言編譯器。該工具能夠將後綴名爲.java的源文件編譯爲後綴名爲.class的能夠運行於Java虛擬機的字節碼。學習
如,咱們有如下簡單的HelloWorld.java
代碼:
public class HelloWorld {
public static void main(String[] args) {
String s = "Hollis";
}
}
複製代碼
經過javac命令生成class文件:
javac HelloWorld.java
複製代碼
生成HelloWorld.class
文件:
如何使用16進制打開class文件:使用
vim test.class
,而後在交互模式下,輸入:%!xxd
便可。
能夠看到,上面的文件就是Class文件,Class文件中包含了Java虛擬機指令集和符號表以及若干其餘輔助信息。
要想可以讀懂上面的字節碼,須要瞭解Class類文件的結構,因爲這不是本文的重點,這裏就不展開說明了。
讀者能夠看到,
HelloWorld.class
文件中的前八個字母是cafe babe
,這就是Class文件的魔數(Java中的」魔數」)
咱們須要知道的是,在Class文件的4個字節的魔數後面的分別是4個字節的Class文件的版本號(第五、6個字節是次版本號,第七、8個字節是主版本號,我生成的Class文件的版本號是52,這時Java 8對應的版本。也就是說,這個版本的字節碼,在JDK 1.8如下的版本中沒法運行)在版本號後面的,就是Class常量池入口了。
Class常量池能夠理解爲是Class文件中的資源倉庫。 Class文件中除了包含類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(constant pool table),用於存放編譯器生成的各類字面量(Literal)和符號引用(Symbolic References)。
因爲不一樣的Class文件中包含的常量的個數是不固定的,因此在Class文件的常量池入口處會設置兩個字節的常量池容量計數器,記錄了常量池中常量的個數。
固然,還有一種比較簡單的查看Class文件中常量池的方法,那就是經過javap
命令。對於以上的HelloWorld.class
,能夠經過
javap -v HelloWorld.class
複製代碼
查看常量池內容以下:
從上圖中能夠看到,反編譯後的class文件常量池中共有16個常量。而Class文件中常量計數器的數值是0011,將該16進制數字轉換成10進制的結果是17。
緣由是與Java的語言習慣不一樣,常量池計數器是從0開始而不是從1開始的,常量池的個數是10進制的17,這就表明了其中有16個常量,索引值範圍爲1-16。
介紹完了什麼是Class常量池以及如何查看常量池,那麼接下來咱們就要深刻分析一下,Class常量池中都有哪些內容。
常量池中主要存放兩大類常量:字面量(literal)和符號引用(symbolic references)。
前面說過,運行時常量池中主要保存的是字面量和符號引用,那麼到底什麼字面量?
在計算機科學中,字面量(literal)是用於表達源代碼中一個固定值的表示法(notation)。幾乎全部計算機編程語言都具備對基本值的字面量表示,諸如:整數、浮點數以及字符串;而有不少也對布爾類型和字符類型的值也支持字面量表示;還有一些甚至對枚舉類型的元素以及像數組、記錄和對象等複合類型的值也支持字面量表示法。
以上是關於計算機科學中關於字面量的解釋,並非很容易理解。說簡單點,字面量就是指由字母、數字等構成的字符串或者數值。
字面量只能夠右值出現,所謂右值是指等號右邊的值,如:int a=123這裏的a爲左值,123爲右值。在這個例子中123就是字面量。
int a = 123;
String s = "hollis";
複製代碼
上面的代碼事例中,123和hollis都是字面量。
本文開頭的HelloWorld代碼中,Hollis就是一個字面量。
常量池中,除了字面量之外,還有符號引用,那麼到底什麼是符號引用呢。
符號引用是編譯原理中的概念,是相對於直接引用來講的。主要包括瞭如下三類常量: * 類和接口的全限定名 * 字段的名稱和描述符 * 方法的名稱和描述符
這也就能夠印證前面的常量池中還包含一些com/hollis/HelloWorld
、main
、([Ljava/lang/String;)V
等常量的緣由了。
前面介紹了這麼多,關於Class常量池是什麼,怎麼查看Class常量池以及Class常量池中保存了哪些東西。有一個關鍵的問題沒有講,那就是Class常量池到底有什麼用。
首先,能夠明確的是,Class常量池是Class文件中的資源倉庫,其中保存了各類常量。而這些常量都是開發者定義出來,須要在程序的運行期使用的。
在《深刻理解Java虛擬》中有這樣的表述:
Java代碼在進行Javac
編譯的時候,並不像C和C++那樣有「鏈接」這一步驟,而是在虛擬機加載Class文件的時候進行動態鏈接。也就是說,在Class文件中不會保存各個方法、字段的最終內存佈局信息,所以這些字段、方法的符號引用不通過運行期轉換的話沒法獲得真正的內存入口地址,也就沒法直接被虛擬機使用。當虛擬機運行時,須要從常量池得到對應的符號引用,再在類建立時或運行時解析、翻譯到具體的內存地址之中。關於類的建立和動態鏈接的內容,在虛擬機類加載過程時再進行詳細講解。
前面這段話,看起來很繞,不是很容易理解。其實他的意思就是: Class是用來保存常量的一個媒介場所,而且是一箇中間場所。在JVM真的運行時,須要把常量池中的常量加載到內存中。
至於到底哪一個階段會作這件事情,以及Class常量池中的常量會以何種方式被加載到具體什麼地方,會在本系列文章的後續內容中繼續闡述。歡迎關注個人博客(www.hollischuang.com) 和公衆號(Hollis),便可第一時間得到最新內容。
另外,關於常量池中常量的存儲形式,以及數據類型的表示方法本文中並未涉及,並非說這部分知識點不重要,只是Class字節碼的分析本就枯燥,做者不想在一篇文章中給讀者灌輸太多的理論上的內容。感興趣的讀者能夠自行Google學習,若是真的有必要,我也能夠單獨寫一篇文章再深刻介紹。
《深刻理解java虛擬機》 《Java虛擬機原理圖解》 1.2.二、Class文件中的常量池詳解(上)