Class類檔案的結構
JAVA實現平臺無關性的基礎是虛擬機器和位元組碼儲存格式,使用Java編譯器可以把Java程式碼編譯為儲存位元組碼的Class檔案,使用JRuby等其他語言的編譯器一樣可以把程式程式碼編譯成Class檔案,虛擬機器並不關心Class的來源是什麼語言,只要它符合Class檔案應有的結構就可以在Java虛擬機器中執行。
Java語言中的各種變數、關鍵字和運算子號的語義最終都是由多條位元組碼命令組合而成的,因此位元組碼命令所能提供的語義描述能力肯定比Java語言本身更強大。Class檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊地排列在Class檔案之中,中間沒有新增任何分隔符,這使得整個Class檔案儲存的內容幾乎全部都是程式執行的必要資料,沒有空隙。當遇到需要佔用8位位元組以上空間的資料項時,則會按照高位在前的方式分割成若干個8位位元組進行儲存。
根據Java虛擬機器規範的規定,Class檔案格式採用一種類似於C語言結構體的偽結構表示。與C語言結構體的域不同,連續的項Java Class檔案中順序儲存,不進行填充或者對齊。在《JVM Specification》中式這樣定義Class檔案的結構:
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
接下來我們一起看看這個表中各個資料項的具體含義。使用下面的測試類進行詳細說明:
編譯完成的類檔案如下:package com.igood; public class TestClass { private int member;//int型別欄位 public static String HOME_URL="http://www.baidu.com";//static字元欄位 public final float PI = 3.14159F;//final欄位 //建構函式 public TestClass(){ } //getMember函式 public int getMember() { return member; } }
1、magic
每個Class檔案的頭4個位元組稱為魔數,它的唯一作用是用於確定這個檔案是否為一個能被虛擬機器接受的Class檔案,它的值為0xCAFEBABE。
2、minor_version和major_version 緊接著魔數的4個位元組儲存的是Class檔案的版本號:第5、6個位元組是次版本號(minor_version),第7、8位元組是主版本號(major_version)。Java的版本號從45開始。
3、常量池
緊接著主次版本號之後的是常量池入口,由於常量池中常量的數量是不固定的,所以再常量池的入口需要放置一項u2的型別的資料,代表常量池容量計數值(constant_pool_count),這個容量計數是從1而不是從0開始的。常量池(constant_pool)儲存了諸如符號常量、final常量值、基本資料型別的字面值等內容。在jVM的標頭檔案jvm/vmcommon/h/pool.h中,有以下對常量池項型別的定義:
#define CONSTANT_Utf8 1 //UTF-8編碼的Unicode字串
#define CONSTANT_Integer 3 //int型別的字面值
#define CONSTANT_Float 4 //float型別的字面值
#define CONSTANT_Long 5 //long型別的字面值
#define CONSTANT_Double 6 //double型別的字面值
#define CONSTANT_Class 7 //對一個類或介面的符號引用
#define CONSTANT_String 8 //String型別字面值的引用
#define CONSTANT_Fieldref 9 //對一個欄位的符號引用
#define CONSTANT_Methodref 10 //對一個類中方法的符號引用
#define CONSTANT_InterfaceMethodref 11 //對一個介面中方法的符號引用
#define CONSTANT_NameAndType 12 //對一個欄位或方法的部分符號引用
constant_pool:表型別資料集合,即常量池中每一項常量都是一個表,共有11種結構各不相同的表結構資料。這11種表都有一個共同的特點,即均由一個u1型別的標誌位開始,可以通過這個標誌位來判斷這個常量屬於哪種常量型別,常量型別及其資料結構如下表所示:
型別 |
簡介 |
專案 |
型別 |
描述 |
CONSTANT_Utf8_info |
utf-8縮略編碼字串 |
tag |
u1 |
值為1 |
length |
u2 |
utf-8縮略編碼字串佔用位元組數 |
||
bytes |
u1 |
長度為length的utf-8縮略編碼字串 |
||
CONSTANT_Integer_info |
整形字面量 |
tag |
u1 |
值為3 |
bytes |
u4 |
按照高位在前儲存的int值 |
||
CONSTANT_Float_info |
浮點型字面量 |
tag |
u1 |
值為4 |
bytes |
u4 |
按照高位在前儲存的float值 |
||
CONSTANT_Long_info |
長整型字面量 |
tag |
u1 |
值為5 |
bytes |
u8 |
按照高位在前儲存的long值 |
||
CONSTANT_Double_info |
雙精度浮點型字面量 |
tag |
u1 |
值為6 |
bytes |
u8 |
按照高位在前儲存的double值 |
||
CONSTANT_Class_info |
類或介面的符號引用 |
tag |
u1 |
值為7 |
index |
u2 |
指向全限定名,常量項的索引 |
||
CONSTANT_String_info |
字串型別字面量 |
tag |
u1 |
值為8 |
index |
u2 |
指向字串字面量的索引 |
||
CONSTANT_Fieldref_info |
欄位的符號引用 |
tag |
u1 |
值為9 |
index |
u2 |
指向宣告欄位的類或介面描述符CONSTANT_Class_info的索引項 |
||
index |
u2 |
指向欄位描述符CONSTANT_NameAndType_info的索引項 |
||
CONSTANT_Methodref_info |
類中方法的符號引用 |
tag |
u1 |
值為10 |
index |
u2 |
指向宣告方法的類描述符CONSTANT_Class_info的索引項 |
||
index |
u2 |
指向名稱及型別描述符CONSTANT_NameAndType_info的索引項 |
||
CONSTANT_InterfaceMethodref_info |
介面中方法的符號引用 |
tag |
u1 |
值為11 |
index |
u2 |
指向宣告方法的介面描述符CONSTANT_Class_info的索引項 |
||
index |
u2 |
指向名稱及型別描述符CONSTANT_NameAndType_info的索引項 |
||
CONSTANT_NameAndType_info |
欄位或方法的部分符號引用 |
tag |
u1 |
值為12 |
index |
u2 |
指向該欄位或方法名稱常量項的索引 |
||
index |
u2 |
指向該欄位或方法描述符常量項的索引 |
4、access_flags(訪問標誌) 佔用2個位元組。用來表明該class檔案中定義的是類還是介面,訪問修飾符是否定義為public;是否定義為abstract型別。類是否是final的。
標誌名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
是否為public型別 |
ACC_FINAL |
0x0010 |
是否被宣告為final,只有類可設定 |
ACC_SUPER |
0x0020 |
是否允許使用invokespecial位元組碼指令,JDK1.2以後編譯出來的類這個標誌為真 |
ACC_INTERFACE |
0x0200 |
標識這是一個介面 |
ACC_ABSTRACT |
0x0400 |
是否為abstract型別,對於介面和抽象類,此標誌為真,其它類為假 |
ACC_SYNTHETIC |
0x1000 |
標識別這個類並非由使用者程式碼產生 |
ACC_ANNOTATION |
0x2000 |
標識這是一個註解 |
ACC_ENUM |
0x4000 |
標識這是一個列舉 |
根據上面的表格,TestClass類的訪問標誌為0x0021 = ACC_PUBLIC | ACC_SUPER = 0x0001 | 0x0020
5、this_class 佔用2個位元組。 它是一個對常量池的索引。指向的是常量池中儲存類名符號引用的CONSTANT_Class_info常量表(見下面常量池具體結構)。比如this_class=0x0001。則表示指向常量池中的第一個常量表。通常這個表是指向當前class檔案所定義的類名。
6、super_class 佔用2個位元組 與this_class類似,指向存放當前class檔案所定義類的超類名字的索引的CONSTANT_Class_info常量表。
7、inteface_count、interfaces
interface_count是class檔案所定義的類直接實現的介面或父類實現的介面的數量。佔2個位元組。intefaces包含了對每個介面的CONSTANT_Class_info常量表的索引。
由於interface_count為0,所以介面索引集合interfaces大小為0,即在編譯後的二進位制檔案中不存在interfaces這項內容。
8、fields_count、fields
fields_count欄位計數器,表明了類中欄位的數量 。fields是不同長度的field_info表的序列。這些field_info表中並不包含超類或父介面繼承而來的欄位。field_info表展示了一個欄位的資訊,包括欄位的名字,描述符和修飾符。如果該欄位是final的,那麼還會展示其常量值。注意,這些資訊有些存放在field_info裡面,有些則存放在field_info所指向的常量池中。fields:欄位表集合,一組欄位表型別資料的集合,欄位表包括access_flags,name_index,descriptor_index,attributes_count和attributes[attributes_count]。
9、method_count、methods
與欄位類似,method_count表明類中方法的數量和每個方法的常量表的索引。methods表明了不同長度的method_info表的序列。methods:方法表集合,一組方法表型別資料的集合。方法表結構和欄位表結構一樣包括access_flags,name_index,descriptor_index,attributes_count和attributes[attributes_count]。
標誌名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
欄位是否為public |
ACC_PRIVATE |
0x0002 |
欄位是否為private |
ACC_PROTECTED |
0x0004 |
欄位是否為protected |
ACC_STATIC |
0x0008 |
欄位是否為static |
ACC_FINAL |
0x0010 |
欄位是否為final |
ACC_SYNCHRONIZED |
0x0020 |
欄位是否為synchronized |
ACC_BRIDGE |
0x0040 |
方法是否是由編譯器產生的橋接方法 |
ACC_VARARGS |
0x0080 |
方法是否接受不定引數 |
ACC_NATIVE |
0x0100 |
欄位是否為native |
ACC_ABSTRACT |
0x0400 |
欄位是否為abstract |
ACC_STRICTFP |
0x0800 |
欄位是否為strictfp |
ACC_SYNTHETIC |
0x1000 |
欄位是否為編譯器自動產生 |
10、attributes_count 和 attributes屬性表
在Class檔案、屬性表、方法表中都可以包含自己的屬性表集合,用於描述某些場景的專有資訊與Class檔案中其它資料項對長度、順序、格式的嚴格要求不同,屬性表集合不要求其中包含的屬性表具有嚴格的順序,並且只要屬性的名稱不與已有的屬性名稱重複,任何人實現的編譯器可以向屬性表中寫入自己定義的屬性資訊。