深入學習Java虛擬機器——類檔案結構

江左煤郎發表於2018-08-27

 Java原始碼由編譯器編譯為所有平臺上的虛擬機器都能統一使用的程式儲存格式——位元組碼檔案,即 .class檔案,通過這種方式,Java語言具備了平臺無關性的特點,Class類檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案按照順序緊湊的排列在Class檔案中,中間沒有任何分隔符。

1. Class類檔案的結構

    1.Class類檔案格式:只包含兩種資料型別,無符號數和表。

    2.無符號數:屬於基本的資料型別,以u1,u2,u4,u8來分別表示1個,2個,4個,8個位元組長度的無符號數,比如數字,索引引用,數量值或按照utf-8編碼的字串值。

    3.表:有多個無符號數或者其他表構成的複合資料型別,以“_info”結尾,用於表示有層次關係的複合結構的資料。

    4. Class類檔案本身就是一張表,它由17個資料項構成:magic,minor_version,major_version,constant_pool_count,constant_pool,access_flags,this_class,super_class,interfaces_count,interfaces,fields_count,fields,methods_count,methods,attributes_count,attributes。無論是無符號數還是表,當需要描述同一型別但數量不定的多個資料時,都會使用一個前置的容器計量數加若干個連續資料項的形式,比如attributes_count與attributes,fields_count與fields,methods_count與methods等。

1.1 magic,minor_version,major_version

    1. magic:是一個無符號數型別的資料項,每個Class檔案的頭4個位元組稱為魔數,該資料項在每個Class類檔案中只有一個,且佔據4位元組。作用是確定這個檔案是否是一個能被虛擬機器接受的Class檔案。也就是說所有位元組碼檔案的前4個位元組是固定的,16進製表示為 0xCAFEBABE。

    2. minor_version和major_version:都是無符號數型別的資料項,在magic之後緊跟的4個位元組就是minor_version和major_version,表示當前位元組碼檔案所使用的jdk版本,minor_version表示次版本號,佔前兩個位元組,後兩個位元組就表示major_version,為主版本號。如 00 00 00 32這一段16進位制碼,也就是4個位元組,前兩個位元組所表示的十進位制數字分別為 minor_version=0x0000=0,後兩個位元組表示的十進位制數字為 major_version=0x0032=50,所以Class檔案的jdk十進位制版本號數字為  50.0,對應的jdk編譯器版本是 jdk 1.6。

1.2 常量池——constant_pool_count,constant_pool

    1. constant_pool_count:是一個無符號數型別的資料項,該資料項緊跟與版本號之後,用於統計常量池中資料項的數量,該資料項只有一個,佔用2個位元組。該項之後,便緊跟常量池中constant_pool。該資料項的計數是從1開始,而不是0,所以真正常量池中的資料項為 constant_pool_count-1。也就是說常量池中資料項的索引值範圍是1~constant_pool_count。

    2. constant_pool:常量池表,緊跟於constant_pool_count之後,這是一個表型別的資料項。常量池是Class檔案的資源倉庫,是Class檔案中與其他資料項關聯最多的資料型別,也是佔用Class檔案空間最大的資料項之一,也是Class檔案中出現的第一個表型別資料項。

    3. 常量池所存常量在原始碼中是什麼:主要為字面量和符號引用。字面量如 123 數字,”ss”等這類文字字串或宣告為final的常量值;符號引用包括了三類,分別是常量類和介面的全限定名欄位的名稱和描述符方法的名稱和描述符

    4. 常量池所儲存的資料型別分類:常量池中的每一個資料項都是表型別,也就是一個表。每個表的開頭都會有一個標誌位tag(只佔用1個位元組),用來指出當前的資料屬於哪一個常量型別的資料。具體型別如下

  1. CONSTANT_Utf8_info:表示utf-8編碼的字串,該型別資料項的表的結構中的資料項為 tag,length,bytes三種。tag即該型別常量的標誌位,十進位制標誌值為1緊接著是length,表示utf8編碼的字串所佔用的位元組數,佔用2個位元組空間接下來就是bytes,每個bytes佔用1個位元組空間,length個bytes就是原utf8編碼的字串的二進位制碼。                                                                                                 注意:由於CONSTANT_Utf8_info型常量被引用與描述Class檔案中的方法,欄位等,所以方法或欄位名的最大長度也就是CONSTANT_Utf8_info的中所儲存位元組碼空間的最大值,最大值即length的最大值就是兩個位元組的空間,也就是說字串的最大長度為64kb,如果方法或變數名超過該長度則無法編譯。
  2. CONSTANT_Integer_info:表示Integer字面量,該型別資料項的表的結構中的資料項為 tag,bytes。                                                其tag十進位制標誌值為3;bytes將按照高位在前儲存int值,4個位元組長度
  3. CONSTANT_Float_info:表示Float型字面量,該型別資料項的表的結構中的資料項為 tag,bytes。                                                    其tag十進位制標誌值為4;bytes將按照高位在前儲存float值,4個位元組長度
  4. CONSTANT_Long_info:表示Long字面量,該型別資料項的表的結構中的資料項為 tag,bytes。                                                       其tag十進位制標誌值為5;bytes按照高位在前儲存long值,8個位元組長度
  5. CONSTANT_Double_info:表示Double字面量,該型別資料項的表的結構中的資料項為 tag,bytes。                                                      其tag十進位制標誌值為6;bytes按照高位在前儲存long值,8個位元組長度
  6. CONSTANT_Class_info:表示類或介面的符號引用,該型別資料項的表的結構中的資料項為 tag,index。                                                      其tag十進位制標誌值為7;index表示指向全限定名常量項的索引,2個位元組長度。
  7. CONSTANT_String_info:表示字串型別字面量,該型別資料項的表的結構中的資料項為 tag,index。                                              其tag十進位制標誌值為8;index表示指向字串字面量的索引,2個位元組長度。
  8. CONSTANT_Fieldref_info:表示欄位的符號引用,該型別資料項的表的結構中的資料項為 tag,indx,indx。                                              其tag十進位制標誌值為9;index表示指向宣告欄位的類或者介面描述符的CONSTANT_Class_info的索引,2個位元組長度;index表示指向欄位描述符CONSTANT_NameAndType_info的索引,2個位元組長度。
  9. CONSTANT_Methodref_info:表示類中方法的符號引用,該型別資料項的表的結構中的資料項為 tag,indx,indx。                                              其tag十進位制標誌值為10;index表示指向宣告方法的類描述符的CONSTANT_Class_info的索引,2個位元組長度;index表示指向名稱及型別描述符CONSTANT_NameAndType_info的索引,2個位元組長度。
  10. CONSTANT_InterfaceMethodref_info:表示介面中方法的符號引用,該型別資料項的表的結構中的資料項為 tag,indx,indx。        其tag十進位制標誌值為11;index表示指向宣告方法的介面描述符的CONSTANT_Class_info的索引,2個位元組長度;index表示指向名稱及型別描述符CONSTANT_NameAndType_info的索引,2個位元組長度。
  11. CONSTANT_NameAndType_info:表示欄位或方法的部分符號引用,該型別資料項的表的結構中的資料項為 tag,indx,indx。         其tag十進位制標誌值為12;index表示指向該欄位或方法名稱常量項的索引,2個位元組長度;index表示指向該欄位或方法描述符的索引,2個位元組長度。
  12. CONSTANT_MethodHandle_info:表示方法控制程式碼,該型別資料項的表的結構中的資料項為 tag,reference_kind,reference_index。其tag十進位制標誌值為15;reference_kind的值再1~9之間,只佔用1個位元組,決定了方法控制程式碼的型別(方法控制程式碼型別的值表示方法控制程式碼的位元組碼行為);reference_index的值必須是對常量池的有效索引,佔用兩個位元組。
  13. CONSTANT_MethodType_info:標示方法型別,該型別資料項的表的結構中的資料項為 tag,descriptor_index。                             其tag十進位制標誌值為16;descriptor_index佔用兩個位元組空間,表示方法的描述符,值必須是對常量池的有效索引且必須是CONSTANT_Utf8_info型別結構的資料項。
  14. CONSTANT_InvokeDynamic_info:表示一個動態方法呼叫點,該型別資料項的表的結構中的資料項為 tag,bootstrap_method_attr_index,name_and_type_index。其tag十進位制標誌值為18;bootstrap_method_attr_index的值必須是當前Class檔案中引導方法表的bootstrap_methods[]陣列的有效索引;name_and_type_index,值必須是對常量池的有效索引且必須是CONSTANT_NameAndType_info型別結構的資料項,表示方法名和方法描述符。 

1.3 訪問標誌——access_flags

    1. access_flags:用於識別一些類或者介面的層次的訪問資訊,包括這個Class是類還是介面,是否定義為public型別是否定義為abstract型別,是否被宣告為final類等。該標誌為佔用2個位元組。具體標示如下

  • ACC_PUBLIC:標誌值為0x0001,表示是否為public型別
  • ACC_FINAL:標誌值為0x0010,表示是否宣告為final,只有類可以
  • ACC_SUPER:標誌值為0x0020,表示是否允許使用invokespecial位元組碼指令
  • ACC_INTERFACE:標誌值為0x0200,表示是否為介面
  • ACC_ABSTRACT:標誌值為0x0400,表示是否為abstarct類
  • ACC_SYNTHETIC:標誌值為0x1000,表示該類不是由使用者編寫的
  • ACC_ANNOTATION:標誌值為0x2000,表示這是一個註解
  • ACC_ENUM:標誌值為0x4000,表示這是一個列舉

    2. 計算access_flags:如果一個類是public,但沒有其他修飾符那麼則該類為ACC_PUBLIC和ACC_SUPER標誌位為真,而其他標誌位為假,所以標誌值為 0x0001|0x0020=0x0021。

1.4 this_class,super_class,interfaces

    1. this_class:類索引,一個佔用2個位元組的資料。指向常量池中資料項結構型別為CONSTANT_Class_info的一個資料項,通過該資料項中的值找到定義在常量池中資料項結構型別為CONSTANT_Utf8_info中的得資料項,再依據此資料項得到全限定名字串。

    2. super_class:父類索引,一個佔用2個位元組的資料。指向常量池中資料項結構型別為CONSTANT_Class_info的一個資料項,通過該資料項中的值找到定義在常量池中資料項結構型別為CONSTANT_Utf8_info中的得資料項,再依據此資料項得到全限定名字串。

    3. interfaces:介面索引集合,表型別資料項,是一組佔用2個位元組的資料。介面索引集合入口第一項為介面計數器(interfaces_count),佔用2個位元組,表示索引表的容量。如果沒有實現任何類,則該值為0。如果實現了介面,則後面會緊跟所實現介面的索引,每個索引都佔兩個位元組,與類相似的方式尋找介面的全限定名。

    4. Class檔案使用this_class,super_class,interfaces這三項資料來確定當前Class檔案所代表的的類的繼承關係。類索引用於確認這類的全限定名,父類索引用於確定父類的全限定名,介面索引集合用來描述該類實現的所有介面。

1.5 fields

    1. fields:欄位表集合,表型別資料項,用於描述類或介面中宣告的欄位。欄位表中的資料項的結構包括u2型別的access_flags,name_index,descriptor_index,attributes_count資料項以及attribute_info型別的attributes資料項這5個資料項構成了欄位表中的單個資料項。

    2. access_flags:該資料項與類中的access_flags資料項相似,都是u2型別。

  • ACC_PUBLIC:標誌值為0x0001,表示是否為public型別
  • ACC_FINAL:標誌值為0x0010,表示是否宣告為final
  • ACC_PRIVATE:標誌值為0x0002,表示是否private
  • ACC_PROTECTED:標誌值為0x0004,表示是否為protected
  • ACC_STATIC:標誌值為0x0008,表示是否為static
  • ACC_TRANSIENT:標誌值為0x0080,表示是否為transient
  • ACC_SYNTHETIC:標誌值為0x1000,表示是否由編譯器自動產生
  • ACC_VOLATILE:標誌值為0x0040,表示是否為volatile
  • ACC_ENUM:標誌值為0x4000,表示是否為enum

    2. name_index,descriptor_index:對常量池的引用,分別代表欄位的簡單名稱以及欄位和方法的描述符。

    3. 欄位表集合不會列出從父類中繼承而來的欄位,但有可能列出原本不存在的欄位,比如內部類中會自動新增指向外部類的例項的欄位。

1.6 methods

    1. mehtods:方法表集合,表型別的資料項,方法表中的每一個資料項的結構類似於欄位表結構,依次包括訪問標誌(access_flags),名稱索引(name_index),描述符索引(descriptor_index),屬性表集合(attributes)和attributes_count。

    2. access_flags:

  • ACC_PUBLIC:標誌值為0x0001,表示是否為public型別
  • ACC_FINAL:標誌值為0x0010,表示是否宣告為final
  • ACC_PRIVATE:標誌值為0x0002,表示是否private
  • ACC_PROTECTED:標誌值為0x0004,表示是否為protected
  • ACC_STATIC:標誌值為0x0008,表示是否為static
  • ACC_BRIDGE:標誌值為0x0040,表示是否是由編譯器產生的橋接方法
  • ACC_SYNTHETIC:標誌值為0x1000,表示是否由編譯器自動產生
  • ACC_SYNCHRONIZED:標誌值為0x0020,表示是否為synchronized方法
  • ACC_NATIVE:標誌值為0x0100,表示是否為native方法
  • ACC_STRICTFP: 0x0800,表示是否為strictfp
  • ACC_VARARGS:0x0080,表示是否接受不定引數

    3. 對於方法中的程式碼:方法的定義可以通過訪問標誌,名稱索引,描述符索引表達清楚,但方法裡面程式碼經過編譯器編譯成位元組碼指令後,存放在方法屬性表集合中一個名為Code的屬性裡面。

    4. attributes_count:表示屬性表集合中屬性的數量

    5. attributes:屬性表型別,其中有attributes_count個屬性項。

    6. 方法表集合的位元組碼分析:首先是方法表的入口——methods_count,一個u2型別的資料,代表著類中的方法數量,然後是方法的訪問標誌值access_flags,接下來就是name_index名稱索引,緊接著是描述符索引descriptor_index,接下來就是屬性表計數器attributes_count,然後就是屬性表中的每一個屬性的名稱索引。

1.7 attributes

    1. attributes:屬性表型別集合,即attribute_info。Class檔案,欄位表,方法表都可由攜帶自己屬性表集合,用以描述某些場景專有的資訊。包括Code屬性,ConstantValue屬性,Exceptions屬性等21個屬性。

 

    2. Code屬性:方法體中的程式碼經過編譯器編譯之後,最終變成位元組碼指令儲存在Code屬性之內。Code屬性出現在方法表的屬性集合之中,但並非所有的方法表都必須存在這個屬性,比如介面中的方法或抽象方法就不存在Code屬性。

    3.ConstantValue屬性:如果一個變數被static和final同時修飾,並且該變數為基本型別或String型別,則會將該變數的位元組碼放進ConstantValue屬性中,如果沒有被final修飾或並非基本型別或String變數,則不會放進。

    4. Exceptions屬性:列舉出方法中可能丟擲的受檢查異常,也就是方法名後緊跟的throws關鍵字後列舉的異常。

    5.LineNumberTable屬性:用於描述Java原始碼行號與位元組碼行號之間的對應關係。最主要的用處就是,當程式丟擲異常時,可以看到出錯的原始碼行號,並且在除錯時,也無法按照原始碼的行號進行設定斷點。

    6.LocalVariableTable屬性:用於描述棧楨中區域性變數與java原始碼中定義的變數之間的關係。

    7. InnerClasses屬性:用於描述內部類與宿主類的關係。

2. 位元組碼指令

位元組碼指令操作的資料型別會有特殊的符號來標記,比如:i 開頭的位元組碼指令代表對int型別的資料操作, l 開頭的位元組碼指令代表對long型別的資料操作,d 開頭的位元組碼指令代表對double型別的資料操作,s 開頭的位元組碼指令代表對short型別的資料操作,b 開頭的位元組碼指令代表對byte或者boolean型別的資料操作,c 開頭的位元組碼指令代表對char型別的資料操作,f 開頭的位元組碼指令代表對float型別的資料操作,a 代表對reference(引用)操作。另外,對於陣列物件,只需在每個型別字母的後面加一a,也就是ia,ba,sa等。資料型別字母后面再跟指令操作的字元,比如iload,iaload。

詳細內容學習推薦:虛擬機器位元組碼指令


相關文章