天天看點

第三章 類檔案結構

  代碼編譯的結果從本地機器碼轉變為位元組碼,是存儲格式發展的一小步,卻是程式設計語言發展的一大步。

  Java 虛拟機使用位元組碼實作了跨平台的願景,不僅針對Java語言,實作了write once,run anywhere的願景;随着發展,越來越多其他語言可以在Java虛拟機之上運作,如Kotlin、Clojure、Groovy、JRbuy、JPython、Scala等。

  Java虛拟機不與包括Java語言在内的任何程式語言綁定,它隻與“Class檔案”這種特定的二進制檔案格式所關聯,Class檔案中包含了Java虛拟機指令集、符号表以及若幹其他輔助資訊。

  

第三章 類檔案結構

  Class檔案是一組以位元組為基礎機關的二進制流,各個資料項目嚴格按照順序緊湊的排列在檔案之中,中間沒有任何分隔符。Class檔案格式采用一種類似于C語言結構體的僞結構來存儲資料,這種僞結構隻有兩種資料類型:無符号數和表。

無符号數屬于基本的資料類型,以u1、u2、u4、u8來分别代表1位元組、2位元組、4位元組和8位元組的無符号數,無符号數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成的字元串值。

表是由多個無符号數或其他表作為資料項構成的複合資料類型,所有的表習慣性以“_info”結尾。表用于描述有層次關系的複合結構資料,整個Class檔案本質上可以視作一張表,這張表嚴格按照下表1-1所列順序排列。

                               表1-1 Class 檔案格式

第三章 類檔案結構

  為了友善講解,準備了如下的Java代碼。  

  相應的類檔案結構如下圖所示:

第三章 類檔案結構

  1.1 魔數與Class檔案版本

  每個Class檔案的頭四個位元組被稱為魔數,用以确定這個檔案是否為一個能被虛拟機接受的Class檔案,值為0x CA FE BA BE。第五六位元組 0x 00 00代表次版本号。主版本号值為0x 00 32,即十進制的50,說明是JDK6或以上版本虛拟機執行的Class檔案。

   

第三章 類檔案結構

   1.2 常量池

  由表1-1可知,主次版本号後,是常量池入口,常量池被比喻為Class檔案裡面的資源倉庫,是Class檔案結構中與其他項目關聯最多的資料。

  首先是u2類型的資料代表常量池容量計數值(constant_pool_count),這個容量技術從1開始,如果不引用任何常量池項目,則把索引值設定為0。由下圖0x 00 16,即十進制的22,代表常量池中有21項常量,索引範圍為1~21。

第三章 類檔案結構

  常量池中主要存放兩大類常量:字面量(Literal)和符号引用(Symbolic References),字面量比較接近于Java語言層面的常量概念,如文本字元串,被聲明為final常量值等。而符号引用則屬于編譯原理方面的概念,主要包括以下幾類常量:

被子產品導出或者開放的包(Package)

類和接口的全限定名(Fully Qualified Name)

字段的名稱和描述符(Descriptor)

方法的名稱和描述符

方法句柄和方法類型(Method Handle、Method Type、Invoke Dynamic)

動态調用點和動态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)

  常量池中的每一項常量都是一個表,常量表中有17種不同類型的常量,所有表結構起始的第一位是個u1類型的标志位,代表目前常量屬于哪種常量類型。

第三章 類檔案結構

  圖6-3常量池結構中,常量池容量0x 00 16後緊跟的u1标志位0x 07查表6-3可知,是一個CONSTANT_Class_info類型,此類型的常量代表一個類或者接口的符号引用。

第三章 類檔案結構

  name_index是常量池的索引值,它指向常量池中一個CONSTANT_Utf8_info類型常量,此常量代表了這個類的全限定名。本例中的name_index值為0x 00 02,指向了常量池中的第二項常量。第二項常量的标志位是0x 01,查表6-3可知,是CONSTANT_Uff8_info類型的常量。其結構表如表6-5所示。

第三章 類檔案結構

  length值說明了UTF-8字元串長度是多少個位元組,後面緊跟長度為length位元組的,UTF-8縮略編碼表示的字元串。

  UTF-8縮略編碼與普通UTF-8編碼的差別是:從'\u0001'到'\u007f'之間的字元(相當于1~127的ASCII碼) 的縮略編碼使用一個位元組表示,從'\u0080'到'\u07ff'之間的所有字元的縮略編碼用兩個位元組表示, 從'\u0800'開始到'\uffff'之間的所有字元的縮略編碼就按照普通UTF-8編碼規則使用三個位元組表示。

  本例中length值長度為0x 00 1D, 長度是29個位元組,往後的29個位元組都在1~127的ASCII碼範圍以内,内容為“org/fenixsoft/clazz/TestClass”

第三章 類檔案結構

  剩下的19個常量與上面讨論的類似,可以通過javap -verbose 類檔案名檢視位元組碼内容,如下圖所示。

第三章 類檔案結構

  不難發現,常量池中存在“I”“V”“<init>”“LineNumberTable”“LocalVariableTable”等, 這些看起來在源代碼中不存在的常量,它們是編譯器自動生成的,會被字段表(field_info)、方法表(method_info)、屬性表(attribute_info)所引用,用于表示方法的傳回值,有幾個參數,每個參數的類型等資訊。

  以下是常量池中的17種資料類型的結構總表

第三章 類檔案結構
第三章 類檔案結構
第三章 類檔案結構

  1.3 通路标志

  由表6-1,常量池結束後,緊接着的2個位元組代表通路标志(access_flags),這個标志用于識别一些類或者接口層次的通路資訊。具體标志值及其含義見下表。

第三章 類檔案結構

  沒有用到的标志位一律為0。由标志位為0x 00 21可知,ACC_PUBLIC 和 ACC_SUPER标志為真。