lass文件在Java體系結構中的位置和做用java
在上一篇博客中, 大體講解了Java虛擬機的體系結構和執行原理。 本篇博客主要講解可以被JVM識別, 加載並執行的class文件的格式。數組
對於理解JVM和深刻理解Java語言, 學習並瞭解class文件的格式都是必需要掌握的功課。 緣由很簡單, JVM不會理解咱們寫的Java源文件, 咱們必須把Java源文件編譯成class文件, 才能被JVM識別, 對於JVM而言, class文件至關於一個接口, 理解了這個接口, 能幫助咱們更好的理解JVM的行爲;另外一方面, class文件以另外一種方式從新描述了咱們在源文件中要表達的意思, 理解class文件如何從新描述咱們編寫的源文件, 對於深刻理解Java語言和語法都是頗有幫助的。 另外, 不論是什麼語言, 只要能編譯成class文件, 都能被JVM識別並執行, 因此class文件不只是跨平臺的基礎, 也是JVM跨語言的基礎, 理解了class文件格式, 對於咱們學習基於JVM的其餘語言會有很大幫助。 學習
總之, 在整個Java技術體系結構中, class文件處於中間的位置, 對於理解整個體系有着承上啓下的做用。 如圖所示:
this
Class文件格式概述編碼
class文件是一種8位字節的二進制流文件, 各個數據項按順序緊密的從前向後排列, 相鄰的項之間沒有間隙, 這樣可使得class文件很是緊湊, 體積輕巧, 能夠被JVM快速的加載至內存, 而且佔據較少的內存空間。 咱們的Java源文件, 在被編譯以後, 每一個類(或者接口)都單獨佔據一個class文件, 而且類中的全部信息都會在class文件中有相應的描述, 因爲class文件很靈活, 它甚至比Java源文件有着更強的描述能力。spa
class文件中的信息是一項一項排列的, 每項數據都有它的固定長度, 有的佔一個字節, 有的佔兩個字節, 還有的佔四個字節或8個字節, 數據項的不一樣長度分別用u1, u2, u4, u8表示, 分別表示一種數據項在class文件中佔據一個字節, 兩個字節, 4個字節和8個字節。 能夠把u1, u2, u3, u4看作class文件數據項的「類型」 。code
class文件中存在如下數據項(該圖表參考自《深刻Java虛擬機》):blog
類型 | 名稱 | 數量 |
u4 | magic | 1 |
u2 | minor_version | 1 |
u2 | major_version | 1 |
u2 | constant_pool_count | 1 |
cp_info | constant_pool | constant_pool_count - 1 |
u2 | access_flags | 1 |
u2 | this_class | 1 |
u2 | super_class | 1 |
u2 | interfaces_count | 1 |
u2 | interfaces | interfaces_count |
u2 | fields_count | 1 |
field_info | fields | fields_count |
u2 | methods_count | 1 |
method_info | methods | methods_count |
u2 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
下面對class文件中的每一項進行詳細的解釋。索引
class文件中的魔數和版本號接口
(1) magic
在class文件開頭的四個字節, 存放着class文件的魔數, 這個魔數是class文件的標誌,他是一個固定的值: 0XCAFEBABE 。 也就是說他是判斷一個文件是否是class格式的文件的標準, 若是開頭四個字節不是0XCAFEBABE, 那麼就說明它不是class文件, 不能被JVM識別。
(2)minor_version 和 major_version
緊接着魔數的四個字節是class文件的此版本號和主版本號。 隨着Java的發展, class文件的格式也會作相應的變更。 版本號標誌着class文件在何時, 加入或改變了哪些特性。 舉例來講, 不一樣版本的javac編譯器編譯的class文件, 版本號可能不一樣, 而不一樣版本的JVM能識別的class文件的版本號也可能不一樣, 通常狀況下, 高版本的JVM能識別低版本的javac編譯器編譯的class文件, 而低版本的JVM不能識別高版本的javac編譯器編譯的class文件。 若是使用低版本的JVM執行高版本的class文件, JVM會拋出java.lang.UnsupportedClassVersionError 。具體的版本號變遷這裏再也不討論, 須要的讀者自行查閱資料。
class文件中的常量池概述
在class文件中, 位於版本號後面的就是常量池相關的數據項。 常量池是class文件中的一項很是重要的數據。 常量池中存放了文字字符串, 常量值, 當前類的類名, 字段名, 方法名, 各個字段和方法的描述符, 對當前類的字段和方法的引用信息, 當前類中對其餘類的引用信息等等。 常量池中幾乎包含類中的全部信息的描述, class文件中的不少其餘部分都是對常量池中的數據項的引用,好比後面要講到的this_class, super_class, field_info, attribute_info等, 另外字節碼指令中也存在對常量池的引用, 這個對常量池的引用當作字節碼指令的一個操做數。 此外, 常量池中各個項也會相互引用。
class文件中的項constant_pool_count的值爲1, 說明每一個類都只有一個常量池。 常量池中的數據也是一項一項的, 沒有間隙的依次排放。常量池中各個數據項經過索引來訪問, 有點相似與數組, 只不過常量池中的第一項的索引爲1, 而不爲0, 若是class文件中的其餘地方引用了索引爲0的常量池項, 就說明它不引用任何常量池項。class文件中的每一種數據項都有本身的類型, 相同的道理,常量池中的每一種數據項也有本身的類型。 常量池中的數據項的類型以下表:
常量池中數據項類型 | 類型標誌 | 類型描述 |
CONSTANT_Utf8 | 1 | UTF-8編碼的Unicode字符串 |
CONSTANT_Integer | 3 | int類型字面值 |
CONSTANT_Float | 4 | float類型字面值 |
CONSTANT_Long | 5 | long類型字面值 |
CONSTANT_Double | 6 | double類型字面值 |
CONSTANT_Class | 7 | 對一個類或接口的符號引用 |
CONSTANT_String | 8 | String類型字面值 |
CONSTANT_Fieldref | 9 | 對一個字段的符號引用 |
CONSTANT_Methodref | 10 | 對一個類中聲明的方法的符號引用 |
CONSTANT_InterfaceMethodref | 11 | 對一個接口中聲明的方法的符號引用 |
CONSTANT_NameAndType | 12 | 對一個字段或方法的部分符號引用 |
每一個數據項叫作一個XXX_info項, 好比, 一個常量池中一個CONSTANT_Utf8類型的項, 就是一個CONSTANT_Utf8_info 。除此以外, 每一個info項中都有一個標誌值(tag), 這個標誌值代表了這個常量池中的info項的類型是什麼, 從上面的表格中能夠看出, 一個CONSTANT_Utf8_info中的tag值爲1, 而一個CONSTANT_Fieldref_info中的tag值爲9 。
Java程序是動態連接的, 在動態連接的實現中, 常量池扮演者舉足輕重的角色。 除了存放一些字面量以外, 常量池中還存放着如下幾種符號引用:
(1) 類和接口的全限定名
(2) 字段的名稱和描述符
(3) 方法的名稱和描述符
在詳細講解常量池中的各個數據項以前, 咱們有必要先了解一下class文件中的特殊字符串, 由於在常量池中, 特殊字符串大量的出現,這些特殊字符串就是上面說的全限定名和描述符。 要理解常量池中的各個數據項, 必須先了解這些特殊字符串。