天天看點

class檔案結構之字段、方法、屬性集合

作者:孫工精品

10、字段表集合

用于描述接口或類中聲明的變量。字段(field)包括類級變量以及執行個體級變量,但是不包括方法内部、代碼塊内部聲明的局部變量。字段叫什麼名字、字段被定義為什麼資料類型,這些都是無法固定的,隻能引用常量池中的常量來描述。它指向常量池索引集合,它描述了每個字段的完整資訊。比如字段的辨別符、通路修飾符(public、private 或 protected)、是類變量還是執行個體變量(static 修飾符)、是否是常量(final 修飾符)等。

注意事項:字段表集合中不會列出從父類或者實作的接口中繼承而來的字段,但有可能列出原本 Java 代碼之中不存在的字段。譬如在内部類中為了保持對外部類的通路性,會自動添加指向外部類執行個體的字段。在 Java 語言中字段是無法重載的,同一個類中兩個字段的資料類型、修飾符不管是否相同,都必須使用不一樣的名稱,但是對于位元組碼來講,如果兩個字段的描述符不一緻,那字段重名就是合法的。

10.1. 字段計數器

字段計數器 的值表示目前 class 檔案 fields 表的成員個數。使用兩個位元組來表示。fields 表中每個成員都是一個 field_info 結構,用于表示該類或接口所聲明的所有類字段或者執行個體字段,不包括方法内部聲明的變量,也不包括從父類或父接口繼承的那些字段

class檔案結構之字段、方法、屬性集合

10.2、字段表

标志名稱 标志值 含義 數量
u2 access_flags 通路标志 1
u2 name_index 字段名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 屬性計數器 1
attribute_info attributes 屬性集合 attributes_count

10.2.1、字段表通路辨別

我們知道,一個字段可以被各種關鍵字去修飾,比如:作用域修飾符(public、private、protected)、static 修飾符、final 修飾符、volatile 修飾符等等。是以,其可像類的通路标志那樣,使用一些标志來标記字段。字段的通路标志有如下這些:

标志名稱 标志值 含義
ACC_PUBLIC 0x0001 字段是否為 public
ACC_PRIVATE 0x0002 字段是否為 private
ACC_PROTECTED 0x0004 字段是否為 protected
ACC_STATIC 0x0008 字段是否為 static
ACC_FINAL 0x0010 字段是否為 final
ACC_VOLATILE 0x0040 字段是否為 volatile
ACC_TRANSTENT 0x0080 字段是否為 transient
ACC_SYNCHETIC 0x1000 字段是否為由編譯器自動産生
ACC_ENUM 0x4000 字段是否為 enum

10.2.2、字段名索引

根據字段名索引的值,查詢常量池中的指定索引項即可。

class檔案結構之字段、方法、屬性集合

10.2.3、描述符索引

描述符的作用是用來描述字段的資料類型、方法的參數清單(包括數量、類型以及順序)和傳回值。根據描述符規則,基本資料類型(byte,char,double,float,int,long,short,boolean)及代表無傳回值的 void 類型都用一個大寫字元來表示,而對象則用字元 L 加對象的全限定名來表示,如下所示:

标志符 含義
B 基本資料類型 byte
C 基本資料類型 char
D 基本資料類型 double
F 基本資料類型 float
I 基本資料類型 int
J 基本資料類型 long
S 基本資料類型 short
Z 基本資料類型 boolean
V 代表 void 類型
L 對象類型,比如:Ljava/lang/Object;
[ 數組類型,代表一維數組。比如:`double[] is [[[D

10.2.3、屬性表集合

一個字段還可能擁有一些屬性,用于存儲更多的額外資訊。比如初始化值、一些注釋資訊等。屬性個數存放在 attribute_count 中,屬性具體内容存放在 attributes 數組中。

// 以常量屬性為例,結構為:
ConstantValue_attribute{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}           

說明:對于常量屬性而言,attribute_length 值恒為 2。

11、方法表集合

方法表集合:指向常量池索引集合,它完整描述了每個方法的簽名。

  • 在位元組碼檔案中,每一個 method_info 項都對應着一個類或者接口中的方法資訊。比如方法的通路修飾符(public、private 或 protected),方法的傳回值類型以及方法的參數資訊等。
  • 如果這個方法不是抽象的或者不是 native 的,那麼位元組碼中會展現出來。
  • methods 表隻描述目前類或接口中聲明的方法,不包括從父類或父接口繼承的方法。另一方面,methods 表有可能會出現由編譯器自動添加的方法,最典型的便是編譯器産生的方法資訊(比如:類(接口)初始化方法<clinit>()和構造器的執行個體初始化方法<init>())。

在 Java 語言中,要重載(Overload)一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特征簽名,特征簽名就是一個方法中各個參數在常量池中的字段符号引用的集合,也就是因為傳回值不會包含在特征簽名之中,是以 Java 語言裡無法僅僅依靠傳回值的不同來對一個已有方法進行重載。但在 Class 檔案格式中,特征簽名的範圍更大一些,隻要描述符不是完全一緻的兩個方法就可以共存。也就是說,如果兩個方法有相同的名稱和特征簽名,但傳回值不同,那麼也是可以合法共存于同一個 class 檔案中。也就是說,盡管 Java 文法規範并不允許在一個類或者接口中聲明多個方法簽名相同的方法,但是和 Java 文法規範相反,位元組碼檔案中卻恰恰允許存放多個方法簽名相同的方法,唯一的條件就是這些方法之間的傳回值不能相同。

11.1、方法計數器

methods_count 的值表示目前 class 檔案 methods 表的成員個數。使用兩個位元組來表示。methods 表中每個成員都是一個 method_info 結構。

class檔案結構之字段、方法、屬性集合

11.2、方法表

methods 表中的每個成員都必須是一個 method_info 結構,用于表示目前類或接口中某個方法的完整描述。如果某個 method_info 結構的 access_flags 項既沒有設定 ACC_NATIVE 标志也沒有設定 ACC_ABSTRACT 标志,那麼該結構中也應包含實作這個方法所用的 Java 虛拟機指令。method_info 結構可以表示類和接口中定義的所有方法,包括執行個體方法、類方法、執行個體初始化方法和類或接口初始化方法,方法表的結構實際跟字段表是一樣的,方法表結構如下:

标志名稱 标志值 含義 數量
u2 access_flags 通路标志 1
u2 name_index 方法名索引 1
u2 descriptor_index 描述符索引 1
u2 attributes_count 屬性計數器 1
attribute_info attributes 屬性集合 attributes_count

方法表通路标志,跟字段表一樣,方法表也有通路标志,而且他們的标志有部分相同,部分則不同,方法表的具體通路标志如下:

标志名稱 标志值 含義
ACC_PUBLIC 0x0001 public,方法可以從包外通路
ACC_PRIVATE 0x0002 private,方法隻能本類通路
ACC_PROTECTED 0x0004 protected,方法在自身和子類可以通路
ACC_STATIC 0x0008 static,靜态方法
class檔案結構之字段、方法、屬性集合

12、屬性表集合

方法表集合之後的屬性表集合,指的是 class 檔案所攜帶的輔助資訊,比如該 class 檔案的源檔案的名稱。以及任何帶有 RetentionPolicy.CLASS 或者 RetentionPolicy.RUNTIME 的注解。這類資訊通常被用于 Java 虛拟機的驗證和運作,以及 Java 程式的調試,一般無須深入了解。此外,字段表、方法表都可以有自己的屬性表。用于描述某些場景專有的資訊。屬性表集合的限制沒有那麼嚴格,不再要求各個屬性表具有嚴格的順序,并且隻要不與已有的屬性名重複,任何人實作的編譯器都可以向屬性表中寫入自己定義的屬性資訊,但 Java 虛拟機運作時會忽略掉它不認識的屬性。

12.1、屬性計數器

attributes_count 的值表示目前 class 檔案屬性表的成員個數。屬性表中每一項都是一個 attribute_info 結構。

12.2.、屬性表

attributes[](屬性表):屬性表的每個項的值必須是 attribute_info 結構。屬性表的結構比較靈活,各種不同的屬性隻要滿足以下結構即可。屬性的通用格式

類型 名稱 數量 含義
u2 attribute_name_index 1 屬性名索引
u4 attribute_length 1 屬性長度
u1 info attribute_length 屬性表

屬性類型:屬性表實際上可以有很多類型,上面看到的 Code 屬性隻是其中一種,Java8 裡面定義了 23 種屬性。下面這些是虛拟機中預定義的屬性:

屬性名稱 使用位置 含義
Code 方法表 Java 代碼編譯成的位元組碼指令
ConstantValue 字段表 final 關鍵字定義的常量池
Deprecated 類,方法,字段表 被聲明為 deprecated 的方法和字段
Exceptions 方法表 方法抛出的異常
EnclosingMethod 類檔案 僅當一個類為局部類或者匿名類時才能擁有這個屬性,這個屬性用于辨別這個類所在的外圍方法
InnerClass 類檔案 内部類清單
LineNumberTable Code 屬性 Java 源碼的行号與位元組碼指令的對應關系
LocalVariableTable Code 屬性 方法的局部變量描述
StackMapTable Code 屬性 JDK1.6 中新增的屬性,供新的類型檢查檢驗器和處理目标方法的局部變量和操作數有所需要的類是否比對
Signature 類,方法表,字段表 用于支援泛型情況下的方法簽名
SourceFile 類檔案 記錄源檔案名稱
SourceDebugExtension 類檔案 用于存儲額外的調試資訊
Synthetic 類,方法表,字段表 标志方法或字段為編譯器自動生成的
LocalVariableTypeTable 是喲很難過特征簽名代替描述符,是為了引入泛型文法之後能描述泛型參數化類型而添加
RuntimeVisibleAnnotations 類,方法表,字段表 為動态注解提供支援
RuntimeInvisibleAnnotations 類,方法表,字段表 用于指明哪些注解是運作時不可見的
RuntimeVisibleParameterAnnotation 方法表 作用與 RuntimeVisibleAnnotations 屬性類似,隻不過作用對象或方法
RuntimeInvisibleParameterAnnotation 方法表 作用與 RuntimeInvisibleAnnotations 屬性類似,隻不過作用對象或方法
AnnotationDefault 方法表 用于記錄注解類元素的預設值
BootstrapMethods 類檔案 用于儲存 invokeddynamic 指令引用的引導方法限定符

Code 屬性:Code 屬性就是存放方法體裡面的代碼。但是,并非所有方法表都有 Code 屬性。像接口或者抽象方法,他們沒有具體的方法體,是以也就不會有 Code 屬性了。Code 屬性表的結構

類型 名稱 數量 含義
u2 attribute_name_index 1 屬性名索引
u4 attribute_length 1 屬性長度
u2 max_stack 1 操作數棧深度的最大值
u2 max_locals 1 局部變量表所需的存續空間
u4 code_length 1 位元組碼指令的長度
u1 code code_lenth 存儲位元組碼指令
u2 exception_table_length 1 異常表長度
exception_info exception_table exception_length 異常表
u2 attributes_count 1 屬性集合計數器
attribute_info attributes attributes_count 屬性集合
class檔案結構之字段、方法、屬性集合

① ConstantValue 屬性

ConstantValue 屬性表示一個常量字段的值。位于 field_info 結構的屬性表中。

ConstantValue_attribute{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;//字段值在常量池中的索引,常量池在該索引處的項給出該屬性表示的常量值。(例如,值是1ong型的,在常量池中便是CONSTANT_Long)
}           

② Deprecated 屬性

Deprecated 屬性是在 JDK1.1 為了支援注釋中的關鍵詞@deprecated 而引入的。

Deprecated_attribute{
    u2 attribute_name_index;
    u4 attribute_length;
}           

④ InnerClasses 屬性

為了友善說明特别定義一個表示類或接口的 Class 格式為 C。如果 C 的常量池中包含某個 CONSTANT_Class_info 成員,且這個成員所表示的類或接口不屬于任何一個包,那麼 C 的 ClassFile 結構的屬性表中就必須含有對應的 InnerClasses 屬性。InnerClasses 屬性是在 JDK1.1 中為了支援内部類和内部接口而引入的,位于 ClassFile 結構的屬性表。

⑤ LineNumberTable 屬性

LineNumberTable 屬性是可選變長屬性,位于 Code 結構的屬性表。LineNumberTable 屬性是用來描述 Java 源碼行号與位元組碼行号之間的對應關系。這個屬性可以用來在調試的時候定位代碼執行的行數。

  • start_pc,即位元組碼行号;1ine_number,即 Java 源代碼行号。

在 Code 屬性的屬性表中,LineNumberTable 屬性可以按照任意順序出現,此外,多個 LineNumberTable 屬性可以共同表示一個行号在源檔案中表示的内容,即 LineNumberTable 屬性不需要與源檔案的行一一對應。

// LineNumberTable屬性表結構:
LineNumberTable_attribute{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 line_number_table_length;
    {
        u2 start_pc;
        u2 line_number;
    } line_number_table[line_number_table_length];
}           

⑥ LocalVariableTable 屬性

LocalVariableTable 是可選變長屬性,位于 Code 屬性的屬性表中。它被調試器用于确定方法在執行過程中局部變量的資訊。在 Code 屬性的屬性表中,LocalVariableTable 屬性可以按照任意順序出現。Code 屬性中的每個局部變量最多隻能有一個 LocalVariableTable 屬性。

  • start pc + length 表示這個變量在位元組碼中的生命周期起始和結束的偏移位置(this 生命周期從頭 e 到結尾 10)
  • index 就是這個變量在局部變量表中的槽位(槽位可複用)
  • name 就是變量名
  • Descriptor 表示局部變量類型描述
// LocalVariableTable屬性表結構:
LocalVariableTable_attribute{
    u2 attribute_name_index;
    u4 attribute_length;
    u2 local_variable_table_length;
    {
        u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index;
        u2 index;
    } local_variable_table[local_variable_table_length];
}           

⑦ Signature 屬性

Signature 屬性是可選的定長屬性,位于 ClassFile,field_info 或 method_info 結構的屬性表中。在 Java 語言中,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數化類型(Parameterized Types),則 Signature 屬性會為它記錄泛型簽名資訊。

⑧ SourceFile 屬性

類型 名稱 數量 含義
u2 attribute_name_index 1 屬性名索引
u4 attribute_length 1 屬性長度
u2 sourcefile index 1 源碼檔案素引

繼續閱讀