對於理解JVM和深入理解Java語言, 學習並了解class檔案的格式都是必須要掌握的功課
Class檔案在Java架構中的位置和作用
對於理解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 |
次要版本 |
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 |
# ##super_class###################1####### |
|
u2 |
#interfaces_count |
|
1 |
u2 |
介面 |
interfaces_count |
u2 |
|
########### # ############fields_count###################1################################1################# # #######field_info##################欄位###### |
fields_count |
|
#u2 |
methods_count |
1 |
##1 |
## method_info
|
|
##methods_count |
||
######## ##### ############u2###################attribute_count############## ##### #1########################attribute_info####### |
屬性 |
#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 | |
## CONSTANT_Class | 7 | #對一個類別或介面的符號引用 |
##CONSTANT_String | 8 | #String類型字面上值 |
CONSTANT_Fieldref | 9 | #對一個欄位的符號參考 |
每個資料項叫做一個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檔案中的特殊字元字串, 因為在常數池中, 特殊字串大量的出現,這些特殊字串就是上面說的全限定名和描述符。 要理解常數池中的各個資料項, 必須先了解這些特殊字串。
以上是Java中class檔案格式的圖文詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!