1. 程式人生 > >位元組碼檔案的內部組成結構

位元組碼檔案的內部組成結構

參考《Java虛擬機器規範Java SE7版》的描述來看,每一個位元組碼檔案其實都對應著全域性唯一的一個類或者介面的定義資訊。位元組碼檔案採用的是一種類似於C語言結構體的偽結構來描述位元組碼檔案格式。為了避免與類的欄位、例項等概念產生混淆,本書將用於描述類結構格式的內容定義為項(item)

         每一項都包括型別、名稱以及該項的數量。型別可以是表明,也可以是“基本型別”。包含在位元組碼檔案中,各項按照嚴格的順序進行連續存放,其內部並不包含任何的分隔符區分段落。在此大家需要注意,這個結構體中只有兩種資料結構,分別是無符號數和表,其中無符號數屬於位元組碼檔案中的“基本型別”,位元組碼檔案中的無符號數有u1/u2/u4/u8,分別表示一個位元組無符號型別、兩個位元組無符號型別、4個位元組無符號型別、8個位元組無符號型別。

         表是由多個無符號數或者其他表作為資料項構成的複合資料型別,所有表的字尾都是使用“_info”進行結尾,並且位元組碼檔案實質上也就是一張表。每一個位元組碼檔案對應著一個ClassFile的結構,如下所示:

                   Class{

                            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]

}

關於ClassFile結構的描述資訊,如下所示:

1)  magic 魔術字元

一個有效地位元組碼檔案的前4個位元組為0xCAFEBABE,(咖啡寶貝),也稱之為魔術字元。JVM用魔術字元來校驗一個目標class檔案是否是合法的。

2)  minor_version(此版本號)和major_version(主版本號)

緊跟在magic之後的4個位元組就是編譯的次版本號和主版本號,他們共同構成了位元組碼檔案的版本號。如果位元組碼檔案的版本號超出了JVM所能夠處理的有效範圍,那麼Java虛擬機器將不會處理這個位元組碼檔案。不過高版本的JVM卻能向下相容執行由低版本JDK編譯的位元組碼檔案。

3)  constant_pool_count(常量池計數器)和constant_pool(常量池)

在位元組碼檔案中,緊跟在次版本號和主版本號之後的就是常量池計數器和常量池。常量池是位元組碼檔案中非常重要的資料項,同時也是位元組碼檔案中與其他項關聯最大和佔用位元組碼空間最大的資料項。常量池主要存放字面量(Literal)和符號引用(Symbolic References)兩大類資料常量,其訪問方式是通過索引來進行訪問的,但由於常量池列表中的數量並不固定,因此在常量池之前就需要通過一個2個位元組的常量池計數器來統計常量池列表中到底擁有多少常量項。在此大家注意,常量池計數器中的計數值並不是從0開始進行計數的,而從1開始,也就是說,如果常量池中有兩個常量時,計數值為2。

常量池中存放的字面量由文字字串、final常量值等構成,而符號引用則包括了類和介面的全限定名(Fully Qualified Name)、欄位的名稱和描述符(Descriptor),以及方法的名稱和描述符。

4)  access_flags(訪問標誌)

緊跟在常量池之後的2個位元組是訪問標誌,訪問標誌就是用於表示某個類或者介面的訪問許可權。比如:訪問標誌指明的是位元組碼檔案中的類還是介面;使用的訪問修飾符是哪一種,是否是由abstract關鍵字修飾的抽象類;如果是被abstract修飾的抽象類,不能再標記為final型別;介面型別同樣也不允許被final修飾。訪問標誌的定義如下所示(僅列舉2項,具體請看書):

訪問標誌

描述

ACC_PUBLIC

0x0001

宣告為public,可以被包的類進行外訪問

ACC_FINAL

0x0010

宣告為final,不允許有派生類

5)  this_class(類索引)和super_class(超類索引)

緊跟在訪問標誌之後的4個位元組就是類索引和超類索引,類索引和超類索引各自會通過索引指向常量池列表中的一個型別為CONSTANT_Class_info的常量項。CONSTANT_Class_info由tag和name_index兩部分組成,tag是一個具有CONSTANT_Class_info值的常量,而name_index則是指向常量池列表中型別為CONSTANT_Utf8_info常量項的索引,通過這個索引即可成功獲取到CONSTANT_Utf8_info常量項中的全限定名字串,如下圖所示。簡單來說,類索引用於確定當前類的全限定名,而超類索引則用於確定當前類的超類的全限定名。

        

6)  interfaces_count(介面計數器)和interface(介面表)

在類索引和超類索引之後的4個位元組就是介面計數器和介面表。介面計數器用於表示當前類或者介面的直接超類介面數量。介面表實際上是一個數組集合,包含了當前類或者介面在常量池列表中直接超類介面的索引集合,通過這個索引即可確定當前類或者介面的超類介面的全限定名。

7)  fields_count(欄位計數器)和fields(欄位表)

在介面計數器和介面表之後就是欄位計數器和欄位表。欄位計數器用於表示一個位元組碼檔案中的field_info表總數,也就是一個類中類變數和例項變數的數量總和。而欄位表實際上則是一個數組集合,欄位表中的每一個成員都必須是一個field_info結構的資料項。簡單來說,field_info用於表達一個欄位的完整資訊,比如欄位的表示符、訪問修飾符(public/private/protected)、是類變數還是例項變數(static 修飾符)、是否是常量(final修飾符)。欄位表中所包含的欄位資訊僅限於當前類或介面的所屬欄位,並不包含繼承超類後的欄位資訊。

8)  methods_count(方法計數器)和methods(方法表)

在欄位計數器和欄位表之後就是方法計數器和方法表。方法計數器用於表示一個位元組碼檔案中的method_info表總數。而方法表實際上是一個數組集合,方法表中的每個成員都必須是一個method_info結構的資料項。簡單來說,method_info用於表示當前類或者介面中某個方法的完整描述,比如方法標示符、方法的訪問修飾符、方法的返回值型別以及方法的引數資訊等。方法表中所包含的方法資訊僅限於當前類或者介面中的所屬方法,並不包含繼承超類後的方法資訊。

9)  attribute_count(屬性計數器)和attributes(屬性表)

在方法計數器和方法表之後的就是屬性計數器和屬性表。屬性計數器用於表示當前位元組碼檔案中的attribute_info表總數。而屬性表同之前的欄位表和方法表一樣都是一個數組集合,屬性表中的每一個成員都必須是一個attribute_info結構的資料項。每一個attribute_info表的第一項都是指向常量池列表中的CONSTANT_Utf8_info項的索引,該表給出了屬性的名稱。

屬性可以出現在ClassFile表、欄位表和方法表中,用以描述與其相關的資訊,比如描述位元組碼檔案中所定義的類和介面相關的資訊、描述與欄位相關的資訊、描述與方法相關的資訊。 

摘自《Java虛擬機器精講》高翔龍