Java類文件結構詳解
概述:
Class文件結構是了解虛擬機的重要基礎之一,如果想深入的了解虛擬機,Class文件結構是不能不了解的。
Class文件是一組以8位字節為基礎單位的二進制流,各項數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,如果是超過8位字節以上空間的數據項,則會按照高位在前的方式(Big-Endian)分割成若幹個8位字節進行存儲。
Class文件中包含了Java虛擬機指令集和符號表以及若幹其他輔助信息。
Class文件格式只有兩種數據類型:無符號數和表。
無符號數屬於基本的數據類型,以u1,u2,u4,u8來分別代表1個字節,2個字節,4個字節和8個字節的無符號數;可用來描述數字,索引引用,數量值或者按照UTF-8編碼構成的字符串值。
表是由多個無符號數或者其他表作為數據項構成的復合數據類型,所有表都習慣性地以“_info”結尾。表用於描述由層次關系的復合結構的數據,整個Class文件本質上就是一張表。
Class文件格式:
表現形式之橫版:
魔數與Class文件的版本:
每個Class文件的頭4個字節成為魔數(Magic Number),它唯一的作用是確定這個文件是否為一個能被虛擬機接受的Class文件。值為:0xCAFEBABE(咖啡寶貝)。
緊接著魔數的4個字節是Class文件的版本號:第5,6字節是次版本號(Minor Version),第7,8字節是主版本號(Major Version)。
常量池:
緊接著主次版本號之後是常量池入口,由於常量池中常量的數量是不固定的,所以在常量池的入口需要放置一個常量池容量計數值(constant_pool_count),這個容量計數是從1而不是0開始的,設計者這樣設計的目的是為了滿足後面某些指向常量池的索引值的數據在特殊情況下需要表達“不引用任何一個常量池項目”的含義。Class文件結構中只有常量池的容量計數是從1開始的,索引集合、字段集合、方法集合、屬性集合的容量計數都是從0開始的。註意,Long和Double型占用兩個計數(這樣設計可能設計時沒想好)。
常量池中主要存放兩大類常量:字面量(Literal)和符號引用。
符號引用屬於編譯原理的概念,包括三類常量:
1,類和接口的全限定名;
2,字段的名稱和描述符;
3,方法的名稱和描述符。
常量池中每一項常量都是一個表,在JDK1.7之後共有14種表結構,它們有一個共同的特點,就是表開始的第一位是一個u1類型的標誌位(tag,取值見下表),代表當前這個常量屬於哪種常量類型。
常量池的項目類型:
類型
標誌
描述
CONSTANT_Utf8_info
1
UTF-8編碼字符串
CONSTANT_Integer_info
3
整型字面量
CONSTANT_Float_info
4
浮點型字面量
CONSTANT_Long_info
5
長整型字面量
CONSTANT_Double_info
6
雙精度浮點型字面量
CONSTANT_Class_info
7
類或接口的符號引用
CONSTANT_String_info
8
字符串類型字面量
CONSTANT_Fieldref_info
9
字段的符號引用
CONSTANT_Methodref_info
10
類中方法的符號引用
CONSTANT_InterfaceMethodref_info
11
接口中方法的符號引用
CONSTANT_NameAndType_info
12
字段或方法的部分符號引用
CONSTANT_MethodHandle_info
15
標識方法句柄
CONSTANT_MethodType_info
16
標識方法類型
CONSTANT_InvokeDtnamic_info
18
表示一個動態方法調用點
這14種常量類型各自有自己的結構,下面列出每個常量項的結構及含義
常量池中的14種常量項的結構總表:
特別說明:
CONSTANT_Fieldref_info ---------> 9 ---------> 字段的符號引用
類的字段都在class文件的字段表集合中存儲
只有初始化值的字段才會在class文件中產生CONSTANT_Fieldref_info,即為此字段的符號引用。因為初始化值會產生相應的指令在<init>或<clinit>方法中,需要持有此字段的符號引用。
引用了其他類(包括父類)的字段時也會在class文件中產生CONSTANT_Fieldref_info,同理是需要持有字段的符號引用。
CONSTANT_Methodref_info ---------> 10 ---------> 類中方法的符號引用
同理,類的方法都在class文件的方法表集合中存儲
只有被調用了的方法(本類的或其它類的)才會產生CONSTANT_Methodref_info,因為需要持有方法的符號引用。
當添加調用了其他類(或本類)的一個方法時,常量池中就會增加4個常量,1是CONSTANT_Methodref_info,即方法的符號引用,2是方法符號引用指向的CONSTANT_NameAndType_info,即方法的部分符號引用,3是方法的名稱,4是方法的描述符。
同理,字段也是這麽個情況。
訪問標誌:
緊接著常量池之後的兩個字節代表訪問標誌(access_flags),用於識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口、是否為public類型、是否為abstract類型、類是否聲明為final等。標誌位及其含義如下表:
標誌名稱
標誌值
含義
ACC_PUBLIC
0X0001
是否為public類型
ACC_FINAL
0X0010
是否被聲明為final,只有類可以設置
ACC_SUPER
0X0020
是否允許使用invokespecial字節碼指令的新語意,invokespecial指令的語意在JDK1.0.2發生過改變,為了區別這條指令使用哪種語意,JDK1.0.2之後編譯
ACC_INTERFACE
0X0200
標誌這是一個接口
ACC_ABSTRACT
0X0400
是否為abstract類型,對於接口或者抽象類來說,此標誌值為真,其他類為假
ACC_SYNTHETIC
0X1000
標誌這個類並非由用戶代碼產生的
ACC_ANNOTATION
0X2000
標誌這是一個註解
ACC_ENUM
0X4000
標誌這是一個枚舉
access_flags中一共有16個標誌位可以使用,當前只定義了其中8個,沒用使用到的標誌位要求一律為0。 access_flages的值即為類滿足上表中的值做或運算得到的值;
類索引、父類索引與接口索引集合:
Class文件中由這三項數據來確定這個類的繼承關系。類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類的全限定名。接口索引集合用來描述這個類實現了哪些接口。
訪問標誌之後順序排列類索引、父類索引、接口索引集合。
接口索引集合入口第一項是u2類型的接口計數器(interfaces_count)表示索引表的容量(即實現了幾個接口)。如果該類沒用實現任何接口,則計數器值為0,後面的接口索引表不再占用任何字節。
圖例:類索引查找全限定名的過程
字段表集合:
排在接口索引集合後邊的是字段計數器:用於標識有多少個字段;接著就是字段表集合。
字段表(field_info)用於描述接口或者類中聲明的變量。
字段包括類級變量以及實例級變量。可以包括的信息有:
字段的作用域(public、private、protected修飾符)
實例變量還是類變量(static修飾符)
可變性(final)
並發可見性(volatile)
可否被序列化(transient)
字段數據類型(基本類型,對象,數組)
字段名稱
字段表結構:
類型
名稱
數量
u2
access_flags
1
u2
name_index
1
u2
descriptor_index
1
u2
attribute_count
1
attribute_info
attributes
attribute_count
字段修飾符放在access_flags項目中,它與類中的access_flags項目非常相似,都是一個u2的數據類型,可以設值的標誌位和含義見下表
字段訪問標誌
標誌名稱
標誌值
含義
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_TRANSIENT
0X0080
字段是否transient
ACC_SYNTHETIC
0X0100
字段是否由編譯器自動產生的
ACC_ENUM
0X0400
字段是否enum
跟隨access_flags標誌的是兩項索引值:name_index和descriptor_index。它們都是對常量池的引用,分別代表字段的簡單名稱以及字段和方法的描述符。 描述符的作用是描述字段的數據類型、方法的參數列表(包括數量、類型及順序)和返回值。根據描述符的規則,基本數據類型以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名表示,見下表
描述符標識字符含義
標識字符
含義
B
基本類型byte
C
基本類型char
D
基本類型double
F
基本類型float
I
基本類型int
J
基本類型long
S
基本類型short
Z
基本類型boolean
V
特殊類型void
L
對象類型。如Ljava/lang/Object
對於數組類型,每一維度將使用一個前置的“[”字符來描述,如“String[][]”,會被記錄為"[[Ljava/lang/String","int[]"被記錄為“[I”。
描述符描述方法時,按照先參數列表,後返回值的順序描述。參數列表按照參數的嚴格順序放置一組小括號“()”內,如void inc()的描述符為“()V”,“viod main(String[] args)”的描述符為“([Ljava/lang/String;)V”,“int indexOf(char[] source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromIndex)”的描述符為“([CII[CIII)I”。
字段表都包含的固定數據項到descriptor_index為止就結束了,不過在descriptor_index之後跟隨著一個屬性表集合用於存儲一些額外的信息,字段都可以在屬性表中描述零至多項的額外信息。有關屬性表的介紹會在後邊具體講解。
字段表集合中不會列出從超類或者父類接口中繼承而來的字段,但有可能列出原本Java代碼之中不存在的字段。
方法表集合:
跟在字段表集合後的是方法計算器:用於標識有多少個方法;緊接著的就是放發表集合。
Class文件存儲格式中對方法的描述與對字段的描述幾乎采用完全一致的方式。
方法表的結構:
類型
名稱
數量
u2
access_flags
1
u2
name_index
1
u2
descriptor_index
1
u2
attribute_count
1
attribute_info
attributes
attribute_count
方法表所包含的數據項目的含義也和字段表集合的非常的類似,僅在訪問標誌和屬性表集合的可選項中有所區別。由於volatile,transient關鍵字不能修飾方法,同時synchronized、native、strictfp和abstract關鍵字可以修飾方法。對於方法表,所有標誌位及其取值如下
方法訪問標誌:
標誌名稱
標誌值
含義
ACC_PUBLIC
0X0001
方法是否public
ACC_PRIVATE
0X0002
方法是否private
ACC_PROTECTED
0X0004
方法是否protected
ACC_STATIC
0X0008
方法是否static
ACC_FINAL
0X0010
方法是否final
ACC_SYNCHRONIZED
0X0020
方法是否synchronized
ACC_BRIDGE
0X0040
方法是否由編譯器產生的橋接方法
ACC_VARARGS
0X0080
方法是否接受不定參數
ACC_NATIVE
0X0100
方法是否為native
ACC_ABSTRACT
0X0400
方法是否為abstract
ACC_STRICTFP
0X0800
方法是否為strictfp
ACC_SYNTHETIC
0X1000
防範是否由編譯器自動產生
通過訪問標誌、名稱索引、描述符索引可清楚的表達方法的定義。那方法裏面的代碼去哪裏了呢?方法裏的Java代碼經過編譯器編譯成字節碼指令後,存放在方法屬性表集合中屬性表中;這個屬性表的名稱為“Code”。屬性表是Class文件格式中最具擴展性的一種數據項目,將在下邊講解。
與字段表集合相對應的,如果父類方法在子類中沒有被重寫(Override),方法表集合中就不會出現來自父類的方法信息,但可能出現編譯器自動添加的方法,最典型的便是類構造器“<clinit>”方法和實例構造器"<init>"方法。
在Java語言中,重載(Overload)一個方法,1、要與原方法具有相同的簡單名稱。2、要與原方法有不同的特征簽名。Java代碼的方法特征簽名只包括方法名稱、參數順序及參數類型;而字節碼的特征簽名還包括方法返回值以及受查異常表。
屬性表集合:
屬性表(attribute_info)在前面的講解中已經出現多次,在Class文件、字段表、方法表、屬性表都可以攜帶自己的屬性表集合,用於描述某些場景專有的信息。與Class文件中其他的數據項目要求嚴格的順序、長度和內容不同,屬性表集合的限制稍微寬松了一些,不再要求各個屬性表具有嚴格順序,並且只要不與已有屬性名重復,任何人實現的編譯器都可以想屬性表中寫入自己定義的屬性信息,Java虛擬機運行時會忽略掉它不認識的屬性。最新的《Java虛擬機規範(Java SE 7)》版中,屬性項已經增加到21項。下邊將介紹一些關鍵常用的屬性。
虛擬機規範預定義的屬性:
屬性名稱
使用位置
含義
Code
方法表
Java代碼編譯成的字節碼指令
ConstantValue
字段表
final關鍵字定義的常量值
Deprecated
類、方法表、字段表
被聲明為deprecated的方法和字段
Exceptions
方法表
方法拋出的異常
EnclosingMethod
類文件
僅當一個類為局部類或者匿名類時才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法
InnerClasses
類文件
內部類列表
LineNumberTable
Code屬性
Java源碼的行號與字節碼指令的對應關系
LocalVariableTable
Code屬性
方法的局部變量描述
StackMapTable
Code屬性
JDK1.6中新增的屬性,供新的類型檢查驗證器(Type Checker)檢查和處理目標方法的局部變量和操作數棧所需要的類型是否匹配
Signature
類、方法表、字段表
JDK1.5中新增的屬性,這個屬性用於支持泛型情況下的方法簽名,在Java語言中,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數化類型(Parameterized Types),則Signature屬性會為它記錄泛型簽名信息。由於Java的泛型采用擦除法實現,在為了避免類型信息被擦除後導致簽名混亂,需要這個屬性記錄泛型中的相關信息
SourceFile
類文件
記錄源文件名稱
SourceDebugExtension
類文件
JDK1.6中新增的屬性,SourceDebugExtension屬性用於存儲額外的調試信息。譬如在進行JSP文件調試時,無法通過Java堆棧來定位JSP文件的行號,JSR-45規範為這些非Java語言編寫,卻需要編譯成字節碼並運行在Java虛擬機中的程序提供了一個進行調試的標準機制,使用SourceDebugExtension屬性就可以用於存儲這個標準所新加入的調試信息
Synthetic
類、方法表、字段表
標識方法或字段為編譯器自動生成的
LocalVariableTypeTable
類
JDK1.5中新增的屬性,它使用特征簽名代替描述符,是為了引入泛型語法之後能描述泛型參數化類型而添加
RuntimeVisibleAnnotations
類、方法表、字段表
JDK1.5新增的屬性,為動態註解提供支持。RuntimeVisibleAnnotations屬性用於註明哪些註解是運行時(實際上運行時就是進行反射調用)可見的
RuntimeInvisibleAnnotations
類、方法表、字段表
JDK1.5新增的屬性,與RuntimeVisibleAnnotations屬性作用剛好相反,用於指明哪些註解是運行時不可見的
RuntimeVisibleParameterAnnotations
方法表
JDK1.5新增的屬性,作用與RuntimeVisibleAnnotations屬性類似,只不過作用對象為方法參數
RuntimeInvisibleParameterAnnotations
方法表
JDK1.5新增的屬性,作用與RuntimeInvisibleAnnotations屬性類似,只不過作用對象為方法參數
AnnotationDefault
方法表
JDK1.5新增的屬性,用於記錄註解類元素的默認值
BootstrapMethods
類文件
JDK1.7中新增的屬性,用於保存invokedynamic指令引用的引導方法限定符
對於每個屬性,它的名稱需要從常量池引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性值的結構則完全自定義的,只需要通過一個u4的長度屬性去說明屬性值所占用的位數即可。一個符合規則的屬性表應該滿足以下定義結構
屬性表結構:
類型
名稱
數量
u2
attribute_name_index
1
u4
attribute_length
1
u1
info
attribute_length
attribute_name_index是指向CONSTANT_Utf8_info類型常量的索引,CONSTANT_Utf8_info類型常量記錄著屬性的名稱;attribute_length標識屬性值所占用的位數。
屬性表集合之Code屬性
Java程序方法體中的代碼經過Javac編譯處理後,最終變為字節碼指令存儲在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_length
u2
exception_table_length
1
exception_info
exception_table
exception_table_length
u2
attribute_count
attribute_info
attributes
attribute_count
attribute_name_index所指向的CONSTANT_Utf8_info類型常量的值固定為“Code”。
attribute_length標識屬性值的總長度。
max_stack代表了操作數棧(Operand Stacks)深度的最大值。
max_locals代表了局部變量所表示的存儲空間(單位是Slot),一個Slot占用32個字節,double或long這種64位的數據類型則需要兩個Slot來存放。方法參數、局部變量、異常變量都需要使用局部變量表來存放。Javac編譯器會根據變量的作用域來分配Slot,每個Slot在整個線程周期可以重復使用,然後根據變量數和作用域計算出max_locals的大小。
code_length和code是用來存儲Java源程序編譯後產生的字節碼指令,code_length代表字節碼長度,既然叫字節碼,每個指令就是一個u1類型的單字節,當虛擬機讀取到code中的一個字節碼時,就可以找出這個字節碼代表的是什麽指令,並且可以知道這條指令後面是否需要跟隨參數,以及參數應當如何理解。一個字節取值範圍為0~255,所以字節碼指令肯定不會超過256個指令,目前Java虛擬機規範定義了其中約200條編碼值對應指令的含義。
因為code_length是一個u4類型,所以理論上每個方法的字節長度不能超過2^23-1,但是虛擬機規範中明確限定了一個方法不能超過65535條字節碼指令,即實際只用到了u2的長度。關於虛擬機字節碼執行的講解將在下一篇博客中詳解。
在字節碼指令之後的是這個方法的顯式異常處理表(下文簡稱異常表)集合,異常表對於Code屬性來說並不是必須存在的。
異常表的結構:
類型
名稱
數量
u2
start_pc
1
u2
end_pc
1
u2
handle_pc
1
u2
catch_type
1
這些字段的含義是如果當字節碼在第start_pc行到end_pc行之間(不含第end_pc行)出現了類型為catch_type或其子類異常(catch_type為指向一個CONSTANT_Class_info型常量的索引),則轉到第handler_pc行繼續處理。當catch_type的值為0時,代表任意異常情況都需要轉向到handler_pc處進行處理。
編譯器使用異常表而不是簡單的跳轉命令來實現Java異常及finally處理機制;在JDK1.4.2之前的Javac編譯器采用了jsr和ret指令實現finally語句,但在1.4.2之後已經改為編譯器自動在每段可能的分支路徑之後都將finally語句塊的內容冗余生成一遍來實現finally語義;在1.7中已經完全禁止jsr和ret指令,如果遇到這兩條指令,虛擬機會在類加載的字節碼校驗階段拋出異常。
下面舉一個異常表的例子(出自深入理解Java虛擬機一書):
package org.fenixsoft.clazz;
public class TestClass {
public int inc() {
int x;
try{
x = 1;
return x;
}catch(Exception e){
x = 2;
return x;
}finally{
x = 3;
}
}
}
編譯後的字節碼及異常表:
Code:
Stack=1, Locals=5, Args_size=1
0: iconst_1 //try塊中的x=1
1: istore_1
2: iload_1 //保存x到returnValue中,此時x=1
3: istore 4
5: iconst_3 //finaly塊中的x=3
6: istore_1
7: iload 4 //將returnValue中的值放到棧頂,準備給ireturn返回
9: ireturn //返回方法的int元素(返回棧頂元素1)
10: astore_2 //給catch中定義的Exception e賦值,存儲在Slot 2中
11: iconst_2 //catch塊中的x=2
12: istore_1
13: iload_1 //保存x到returnValue中,此時x=2
14: istore 4
16: iconst_3 //finally塊中的x=3
17: istore_1
18: iload 4 //將returnValue中的值放到棧頂,準備給ireturn返回
20: ireturn //返回方法的int元素(返回棧頂元素2)
21: astore_3 //如果出現了不屬於Exception及其子類的異常才會走到這裏
22: iconst_3 //finally塊中的x=3
23: istore_1
24: aload_3 //將異常放置到棧頂
25: athrow //拋出異常
Exception table:
from to target type
5 10 Class java/lang/Exception //第0到第5行如果拋出Exception異常則跳轉到第10行
5 21 any //第0到第5行如果拋出任何異常則跳轉到第21行
16 21 any //第10到第16行如果拋出任何異常則跳轉到第21行
編譯器為這段Java源碼生成了3條異常表記錄,對應3條可能出現的代碼執行路徑。從Java代碼的語義上講,這3條執行路徑分別為:
如果try語句塊中出現屬於Exception或其子類的異常,則轉到catch語句塊處理。
如果try語句塊中出現不屬於Exception或其子類的異常,則轉到finally語句塊處理。
如果catch語句塊中出現任何異常,則轉到finally語句塊處理。
字節碼0~4行所做的操作數就是將整數1賦值給變量x,並將此時x的值復制一份到最後一個本地變量表的Slot中(這個Slot裏面的值在ireturn指令執行前將會被重新讀到棧頂,作為方法返回值使用,這裏暫且就記為returnValue)。如果這時沒有出現異常,則會繼續走到第5~9行,將變量x賦值為3,然後將之前保存到returnValue中的整數1讀入到操作棧頂,最後ireturn指令會以int形式放回操作棧頂中的值,方法結束。如果出現了異常,PC寄存器指針轉到第10行,第10~20行所做的事情是將2賦值給變量x,然後將變量x此時的賦值給returnValue,最後再將變量x的值改為3。方法返回前同樣將returnValue中保留的整數2讀到操作棧頂。從第21行開始的代碼,作用是將變量x的值賦為3,並將棧頂的異常拋出,方法結束。
屬性表集合之Exception屬性
這裏的Exception屬性是在方法表中與Code屬性平級的一項屬性,不要與前面剛剛講解完的異常表產生混淆。Exception屬性的作用是列舉出方法中可能拋出的受查異常(Checked Exceptions), 也就是方法描述時在throws關鍵字後面列舉的異常。
Exception屬性表結構:
類型
名稱
數量
u2
attribute_name_index
1
u4
attribute_length
1
u2
number_of_exceptions
1
u2
exception_index_table
number_of_exceptions
number_of_exceptions表示方法可能拋出number_of_exceptions種受查異常,每一種受查異常使用一個exception_index_table項表示,exception_index_table是一個指向常量池中CONSTANT_Class_info型常量的索引,代表該受查異常的類型。
屬性表集合之LineNumberTable屬性
LineNumberTable屬性用於描述Java源碼行號與字節碼行號(字節碼的偏移量)之間的對應關系。
LineNumberTable屬性表結構:
類型
名稱
數量
u2
attribute_name_index
1
u4
attribute_length
1
u2
line_number_table_length
1
line_number_info
line_number_table
line_number_table_length
line_number_table是一個數量為line_number_table_length,類型為line_number_info的集合,line_number_info表包括了start_pc和line_number兩個u2類型的數據項,前者是字節碼行號,後者是Java源碼行號。
屬性表集合之LocalVariableTable屬性
LocalVariableTable屬性用於描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關系。
LocalVariableTable屬性結構:
類型
名稱
數量
u2
attribute_name_index
1
u4
attribute_length
1
u2
local_varible_table_length
1
local_variable_info
local_variable_table
local_varible_table_length
local_variable_info項目代表了一個棧幀與源碼中的局部變量的關聯,結構見下表:
類型
名稱
數量
u2
start_pc
1
u2
length
1
u2
name_index
1
u2
descriptor_index
1
u2
index
1
start_pc和length屬性分別代表了這個局部變量的生命周期開始的字節碼偏移量及其作用範圍覆蓋的長度,兩者結合起來就是這個局部變量在字節碼之中的作用域範圍。
name_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info型常量的索引,分別代表了局部變量的名稱及這個局部變量的描述符。
index是這個局部變量在棧幀局部變量表中Slot的位置。當這個變量數據類型是64位類型時(double和long),它占用的Slot為index和index+1兩個
在JDK1.5引入泛型之後,LocalVariableTable屬性增加了一個“姐妹屬性”:LocalVariableTypeTable,這個新增屬性的結構與LocalVariableTable非常相似,僅僅是把記錄的字段描述符的descriptor_index替換成了字段的特征簽名(Signature),對於非泛型來說,描述符和特征簽名能描述的信息是基本一致的。
屬性表集合之SourceFile屬性
SourceFile屬性用於記錄生成這個Class文件的源碼文件名稱。
sourceFile屬性結構:
類型
名稱
數量
u2
attribute_name_index
1
u4
attribute_length
1
u2
sourcefile_index
1
sourcefile_index數據項是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼文件的文件名。
屬性表集合之ConstantValue屬性
ConstantValue屬性的作用是通知虛擬機自動為靜態變量賦值。只有被static關鍵字修飾的常量(類變量)才可以使用這項屬性。目前Sun Javac編譯器的選擇是:如果同時使用final和static來修飾一個變量,並且這個變量的數據類型是基本類型或者java.lang.String的話,就生成ConstantValue屬性來進行初始化,如果這個變量沒有被final修飾,或者並非基本類型及字符串,則將會選擇在<clinit>方法中進行初始化。
對ConstantValue的屬性值只能限於基本類型和String。
ConstantValue屬性結構:
類型
名稱
數量
u2
attribute_name_index
1
u4
attribute_length
1
u2
constantvalue_index
1
屬性表集合之InnerClasses屬性
InnerClasses屬性用於記錄內部類與宿主類之間的關聯。
InnerClasses屬性結構:
類型
名稱
數量
u2
attribute_name_index
1
u4
attribute_length
1
u2
number_of_classes
1
inner_classes_info
inner_classes
number_of_classes
number_of_classes代表需要記錄多少個內部類信息。
inner_classes_info表的結構:
類型
名稱
數量
u2
inner_class_info_index
1
u2
outer_class_info_index
1
u2
inner_name_index
1
u2
inner_class_access_flags
1
inner_class_info_index和outer_class_info_index分別代表了內部類和宿主類的符號引用。inner_name_index代表內部類的名稱,inner_class_access_flags是內部類的訪問標誌。
屬性表集合之Deprecated及Synthetic屬性
兩個屬性都屬於標誌類型的布爾屬性,只存在有和沒有的區別,沒有屬性值的概念。Synthetic代表字段或者方法並不是有Java源碼直接產生的,而是由編譯器自行添加的。
屬性表集合之StackMapTable屬性
StackMapTable屬性在JDK1.6發布後增加到了Class文件規範中,它是一個復雜的變長屬性,位於Code屬性的屬性表中。會在虛擬機類加載的字節碼驗證階段被新類型檢查驗證器(Type Checker)使用,目的在於代替以前比較消耗性能的基於數據流分析的類型推導驗證器。一個方法的Code屬性最多只能有一個StackMapTable屬性。
屬性表集合之Signature屬性
Signature屬性在JDK1.5增加到Class文件規範之中,用於記錄泛型簽名信息。Java語言的泛型采用的是擦除法實現的偽泛型,缺點就是運行期做反射時無法獲得到泛型信息,Signature屬性就是為了彌補這個缺陷而增設的。
Signature屬性的結構:
類型
名稱
數量
u2
attribute_name_index
1
u4
attribute_length
1
u2
signature_index
1
signature_index的值必須是一個對常量池的有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示類簽名、方法類型簽名或字段類型簽名。
屬性表集合之BootstrapMethods屬性
BootstrapMethods屬性在JDK1.7增加到Class文件規範之中的。它是一個復雜的變長屬性,位於類文件的屬性表中。用於保存invokedynamic指令引用的引導方法限定符。
---------------------
作者:煩啦
來源:CSDN
原文:https://blog.csdn.net/A_zhenzhen/article/details/77977345
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
Java類文件結構詳解