天天看點

Class檔案

一、java class檔案是什麼

《the javatm virtual machine specification》(second edtion)中有表述:java class檔案由8位位元組流組成,所有的16位、32位和64位資料分别通過讀入2個、4個和8個位元組來構造,多位元組資料總是按照big-endian順序來存放,即高位位元組在前(放在低位址)。每個class檔案都包含且僅包含一個java類型(類或者接口)。

或許,《the javatm virtual machine specification》中的表述不夠明确,那麼我們可以參考一下《inside the java virtual machine》(second edtion)中的表述:java class檔案特指以.class為字尾名的java虛拟機可裝載的檔案。

分析一下兩者的表述,我覺得都不夠全面、不夠明确。我是這麼定義的:java class檔案就是指符合特定格式的位元組流組成的二進制檔案。這個特定的格式就是指第二節要讨論的class檔案格式,亦即在《the javatm virtual machine specification》中定義的class檔案格式。從另一個角度來說,這個特定格式就是指jvm能夠識别、能夠裝載的格式。為什麼這麼說呢?因為jvm在裝載class檔案時,要進行class檔案驗證,以保證裝載的class檔案内容符合正确的内部結構。這個内部結構指的就是這個特定格式,隻要是符合這個特定格式的class檔案都是合法的、規範的class檔案,都是jvm能夠裝載的class檔案。如果覺得這樣的表述還是不夠明确,我隻能建議你讀完這篇文章之後再回頭來了解看看了j

為了讨論友善,在下文中将對這兩個參考資料做個簡記:

1)《the java virtual machine specification》(second edtion)簡記為《jvm spec》(2nded)。

2)《inside the java virtual machine》(second edtion) 簡記為《inside jvm》(2nded)。

二、java class檔案的格式

在講class檔案的格式之前,要介紹三個概念:

1)資料類型:《jvm spec》(2nded)中指出,java class檔案的資料用自己定義的一個資料類型集來表示,即u1,u2,u4,分别用于表示一個無符号類型的、占1,2,4個位元組的資料。在《inside jvm》(2nded)一書中,作者把這個資料類型集稱之為class檔案的基本類型,本人覺得比較形象,便于了解。是以,在本文中,我們也用基本類型來表示java class檔案的資料。

2)表:根據《jvm spec》(2nded)中的定義,表(table)由項(定義見3)組成,用于幾種class檔案結構中。《jvm spec》(2nded)中指出,java class檔案格式用一個類似于c結構的記号編寫的僞結構來表示。這個僞結構指的就是這裡的表,例如下面的classfile表就是這種僞結構的一個典型例子,下文中所有的表都是指這種僞結構的表。表的大小是可變的,這是因為它的組成部分項是可變的。注意;這裡的可變是針對class層次而言的,即在不同的class檔案中該項的大小可能不一樣的,但是對于每一個具體的class檔案來說,這個項的大小又是一定的,因而這個表的大小也是一定的。那麼,項為什麼是可變的呢?請看下面的分析。

3)項:描述java class檔案格式的結構的内容稱為項(items)。每個項都有自己的類型和名稱。項的類型可能是基本類型,也可能是一個表的名字,這種項都是一些數組項。數組項的每一個元素都是一個表,這個表同頂層的classfile表一樣,也都是一種僞結構,也都是由一些項構成的,而且這些表不一定是同一種格式的,是以數組項也可以看作一個可變大小的結構流j。這些表對于該數組項來說就是子項,當然子項可能還有子項(目前子項的深度最多就兩層)。項的名稱,沒有什麼好說的,就是《jvm spec》(2nded)中指定的一些名稱。另外,項也是有大小的,對于沒有子項的項來說,其大小是固定的;對于有子項的項來說,其大小是可變的。在一個具體的class檔案中,一個可變項(數組)的大小都會在其前一項中指定,為什麼會是這樣的呢?因為《jvm spec》(2nded)中就是這麼定義的!在class檔案中,每個項按規範中定義好的順序存儲在class檔案中,相鄰的項之間沒有任何間隔,連續的項(數組)也是按順序存儲,不進行填充或者對齊,這樣可以使class檔案緊湊。

好了,我想這三個概念我已經解釋地比較清楚了,下面開始正式解析class檔案的格式。

首先要來解析一下classfile表結構,這是《jvm spec》(2nded)中定義的class檔案最外層的結構,換言之,就是class檔案的格式。

classfile表結構

    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];

    }

classfile表結構由16個不同的項組成,其中的各項可以簡要地分析如下:

(1) magic

每個class檔案的前4個位元組被稱為它的魔數(magic number): 0xcafebabe。魔數的作用在于:可以輕松地分辨出java class檔案和非java class檔案。(如果一個檔案不是以0xcafebabe開頭,它就肯定不是java class檔案,因為它不符合規範j)。當java還稱為“oak”的時候,這個魔數就已經定下來了,它預示了java這個名字的出現。魔數的來曆請大家自己查閱j

(2) minor_version和major_version

class檔案的下面4個位元組包含了次、主版本号。通常隻有給定主版本号和一系列次版本号後,java虛拟機才能夠讀取class檔案。如果class檔案的版本号超出了java虛拟機所能夠處理的有效範圍,java虛拟機将不會處理該class檔案。例如j2se5.0版本的虛拟機就不能執行由j2se6.0版本的編譯器編譯出來的class檔案。

(3) constant_pool_count

版本号後面的項是constant_pool_count即常量池計數項,該項的值必須大于零,它給出該class檔案中常量池清單項的元素個數,這個計數項包括了索引為0的constant_pool表項,但是該表項不出現在class檔案的constant_pool清單中,因為它被保留為java虛拟機内部實作使用了,是以常量池清單的元素個數constant_pool_count-1,各個常量池表項的索引值分别為1到constant_pool_count-1。

注:在這裡,有幾個術語需要解釋一下,常量池即為constant_pool,常量池清單就是指constant_pool[ ],常量池表項即指常量池清單中的某一個具體的表項(元素)。這些常量池表項的可能類型如下述的cp_type表所示:

cp_type

入口類型                                标志值

constant_class                           7

constant_fieldref                        9

constant_methodref                       10

constant_interfacemethodref              11

constant_string                           8

constant_integer                          3

constant_float                            4

constant_long                             5

constant_double                           6

constant_nameandtype                      12

constant_utf8                              1

(4) constant_pool[ ]

constant_pool_count項下面是constant_pool[ ]項,即常量池清單,其中存儲了該classfile結構及其子結構中引用的各種常量,諸如文字字元串、final變量值、類名和方法名等等。在java class檔案中,常量池表項是用一個cp_info結構來描述的,常量池清單就是由constant_pool_count-1個連續的、可變長度的cp_info表結構構成的constant_pool[ ]數組。為什麼是constant_pool_count-1個constant_pool的原因,在上面已經解釋了。每一個常量池表項都是一個變長結構,其通常格式如下所示:

cp_info

cp_info {

        u1 tag;

        u1 info[];

cp_info表的tag項是一個無符号的byte類型值,它表明了cp_info表的類型和格式,具體的tag類型見上表。

需要說明的是,cp_info隻是一個抽象的概念,在class檔案中,它表現為一系列具體的、形如constant_xxxx_info的constant_pool結構,其具體的格式由cp_info表的tag項(即第一個位元組)來确定。不同的cp_info表,其info[]項也是不一樣的,例如,constant_class_info表的info[]項為“u2 name_index”,而constant_utf8_info表的info[]項為“u2 length; u1 bytes[length];”,顯然,這兩個cp_info表是不一樣的,大小更是不一樣的,因而常量池表項的大小是可變的。由于常量池清單中的每個常量池表項的結構是不一樣,是以常量池清單的大小也是可變的。在class檔案中,常量池清單項是一個可變長度的結構流。

由cp_info表以及cp_type表我們可以知道,若cp_info表中tag(标志)項的值為1時,目前的cp_info就是一個constant_utf8_info表結構,若cp_info表中tag項的值為3,目前的cp_info就是一個constant_integer_info表結構,其它情況類推。這些表的結構可以查閱《jvm spec》(2nded)的第四章或者《inside jvm》(2nded)的第六章。

(5) access_flags

緊接常量池後的兩個位元組稱為access_flags,access_flags項描述了該java類型的一些通路标志資訊。例如,通路标志指明檔案中定義的是類還是接口;通路标志還定義了在類或接口的聲明中,使用了哪些修飾符;類和接口是抽象的還是公共的等等。實際上,access_flags項的值是java類型聲明中使用的通路标志符的掩碼(mask,這裡掩碼指的是access_flags的值是所有通路标志值的總和,當然,未被使用的标志位在class檔案中都被設定為0。例如,若access_flags的值就是0x0001,就表示該java類型的通路标志符是acc_public;若access_flags的值是0x0011,就表示該java類型的通路标志符是acc_public和acc_final,因為隻有這兩個标志位的和才可能是0x0011;其它情況類推)。

一個java類型的所有access_flags标志符如下表所示:

access_flags

标志名稱         值           含義

acc_public     0x0001   聲明為public,可以從它的包外通路

acc_final      0x0010   聲明為final,不允許有子類

acc_super      0x0020   用invokespecial指令處理超類的調用

acc_interface  0x0200   表明是一個接口,而不是一個類

acc_abstract   0x0400   聲明為abstract,不能被執行個體化

需要說明的是,這是針對一個java類型的通路标志符清單,有的标志符隻有類可以使用,有的标志符隻有接口才可以使用,詳情請查閱《jvm spec》(2nded)。

(6) this_class

接下來的兩個位元組為this_class項,其值為一個對常量池表項的索引,即它指向一個常量池表項,而且該常量池表項必須為constant_class_info表的結構。該表有一個name_index項,該項将指向另一個常量池表項,該表項包含了該類或者接口的完全限定名稱。

(7) super_class

緊接着this_class之後的兩個位元組是super_class項,該項必須是對常量池表項的一個有效索引或者值為0。如果super_class項的值為0,則該class檔案必須表示java.lang.object類。如果super_class項的值不為0,則又分為兩種情況,若該class檔案表示一個類,則super_class項必須是對常量池中該類的超類的constant_class_info表項的索引,這個超類和它的任何超類都不能是一個final類;若該class檔案表示一個接口,則super_class項必須是對常量池中表示java.lang.object類的一個constant_class_info表項的索引。

(8) interfaces_count和interfaces[ ]

緊接着super_class項後面的兩個位元組是interfaces_count項,此項表示由該類直接實作或者由該接口所擴充的超接口的數量。

緊接着interfaces_count項後面的是interfaces清單項,它包含了由該類直接實作或者由該接口所擴充的超接口的常量池索引,共計interfaces_count個索引。interfaces清單中的常量池索引按照該類型在源代碼中給定的從左到右的順序排列。

(9) fields_count和fields[ ]

接下來的是fields_count項,該項的值給出了fields清單項中的field_info表結構的數量,即表示了該java類型聲明的類變量和執行個體變量的個數總和。

fields清單項包含了在該java類型中聲明的所有字段的完整描述。fields清單中的每個field_info表項都完整地表示了一個字段的資訊,包括該字段的名稱、描述符和修飾符等。這些資訊有的放在field_info表中,如修飾符;有的則放在field_info表所指向的常量池中,如名字和描述符。同前面的分析,fields清單項也是一個變長結構。   

需要說明的是,隻有在該java類型中聲明的字段才可能在fields清單中列出,fields清單中不包括從超類或者超接口中繼承而來的字段資訊。

(10) methods_count和methods[ ]

在class檔案中,緊接着fields後面的是對在該java類型中所聲明的方法的描述。首先是methods_count項,它占兩個位元組長度,它的值表示對該java類型中聲明的所有方法的總計數。methods_count項後面是methods清單項,它由methods_count個連續的method_info表構成。每個method_info表都包含了與一個方法相關的資訊,如方法名、描述符(即方法的傳回值及參數類型)以及一些其它資訊。如果一個方法既非abstract也非native,那麼該method_info表将包含該方法局部變量所需的棧空間長度、為方法所捕獲的異常表、位元組碼序列以及可選的行号表和局部變量表等資訊。

需要說明的是,隻有在該java類型中顯式定義的方法才可能在fields清單中列出,fields清單中不包括從超類或者超接口中繼承而來的方法資訊。

(11) attributes_count和attributes[ ]

class檔案中最後的部分是屬性(attribute),它給出了在該java類型中所定義的屬性的基本資訊。首先是attributes_count項,它占兩個位元組長度,它的值表示在後續的attributes清單中的attributes_info表的總個數。每個attributes_info表的第一項都是對常量池中constant_utf8_info表項的一個索引,該表給出了此屬性的名稱。

需要說明的是,屬性有很多種,在class檔案中的很多地方都出現了屬性這一項,在頂層classfile表中有attributes屬性項,在field_info表中也有attributes屬性項,在method_info中也有attributes屬性項,但是它們各有各的功能,詳見上述分析。在《jvm spec》(2nded)中,為classfile表結構的attributes清單項定義的唯一屬性是sourcefile屬性,為field_info表結構的attributes清單項定義的唯一屬性是constantvalue屬性,為method_info表結構的attributes清單項定義的屬性是code屬性和exceptions屬性。

總而言之,class檔案格式是一個規範性的格式。這個規範指的就是,上面提到的這些表結構本身的規範性,以及這些表結構之間的包含關系的規範性。實際上,《jvm spec》(2nded)中就是通過表和項這兩個概念來組織class檔案的格式的。首先,classfile表就是class檔案最外層的結構,換言之,這就是class檔案的格式。其次,classfile表又是一些項組成的,這些項的内容都要符合《jvm spec》(2nded)中定義的規範,具體來說,若這個項的類型是基本類型,該項的值要符合規範,例如magic項一定要是0xcafebabe,access_flags項的值一定要是有效的标志值等等;若這個項的類型是一個表名,即該項是一個數組項,那麼該數組項清單中的每一個表項都要是一個合法的、規範的表,不能是一個規範中沒有定義的新表,這就是包含關系的規範性,同樣,清單項中的每個表項本身也都要是符合其規範定義的表項,例如常量池清單中的某個constant_class_info表的name_index項不是對一個constant_utf8_info表結構的索引,那麼這個常量池的表項就不是一個合法的表項,因而這個常量池清單項就是不符合規範的,因而整個檔案就是不符合規範的。