Class檔案在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檔案處於中間的位置, 對於理解整個體系有著承上啟下的作用。 如圖所示:
Class檔案格式概述
class檔案是一種8位位元組的二進位流檔案, 各個資料項目按順序緊密的從前向後排列, 相鄰的項之間沒有間隙, 這樣可以使得class檔案非常緊湊, 體積輕巧, 可以被JVM快速的載入至記憶體, 並且佔據較少的記憶體空間。 我們的Java源檔案, 在被編譯之後, 每個類(或者介面)都單獨佔據一個class檔案, 並且類中的所有資訊都會在class檔案中有相應的描述, 由於class檔案很靈活, 它甚至比Java源檔案有著更強的描述能力。
class檔案中的資訊是一項一項排列的, 每項資料都有它的固定長度, 有的佔一個位元組, 有的佔兩個位元組, 還有的佔四個位元組或8個位元組, 資料項目的不同長度分別用u1, u2, u4, u8表示, 分別表示一種資料項目在class檔案中佔據一個位元組, 兩個位元組, 4個位元組和8個位元組。 可以把u1, u2, u3, u4看做class檔案資料項目的“類型” 。
class檔案中存在以下資料項目(該圖表參考自《深入Java虛擬機器》):
| 類型 |
名稱 |
數量 |
| 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檔案中的特殊字元串, 因為在常量池中, 特殊字元串大量的出現,這些特殊字元串就是上面說的全限定名和描述符。 要理解常量池中的各個資料項目, 必須先瞭解這些特殊字元串。
對於class檔案的講解會在後續博文中繼續, 歡迎關注。
===========================================================================================
原文出自:http://blog.csdn.net/zhangjg_blog/article/details/21486985
===========================================================================================