JVM八:class檔案結構(2)
下面我們接著為訪問標誌,類索引,父類索引,介面索引集合,欄位集合,方法表集合
訪問標誌:
常量池結束後緊接著的兩個位元組代表訪問標誌,用來標識一些類或介面的訪問資訊,包括:這個Class是類還是介面;是否定義為public;是否定義為abstract;如果是類的話,是否被宣告為final等。具體的標誌位以及含義如下表:
標誌名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
是否是public |
ACC_FINAL |
0x0010 | 是否被宣告為final,只有類可以設定 |
ACC_SUPER |
0x0020 |
是否允許使用invokespecial位元組碼指令的新語義,JDK1.0.2之後編譯出來的類的這個標誌預設為真 |
ACC_INTERFACE |
0x0200 |
標識是一個介面 |
ACC_ABSTRACT |
0x0400 |
是否是abstract,對於介面和抽象類來說為真,其他類都為假 |
ACC_SYNITHETIC |
0x1000 |
標識這個類並非由使用者程式碼產生 |
ACC_ANNOTATION |
0x2000 |
標識這是一個註解 |
ACC_ENUM |
0x4000 |
標識這是一個列舉類 |
類索引(2個位元組)、父類索引(2個位元組)與介面索引(2個位元組介面數+介面)集合:
在訪問標誌access_flags後接下來就是類索引(this_class)和父類索引(super_class),這兩個資料都是u2型別的,而接下來的介面索引集合是一個u2型別的集合,class檔案由這三個資料項來確定類的繼承關係。由於Java中是單繼承,所以父類索引只有一個;但Java類可以實現多個介面,所以介面索引是一個集合。
類索引用來確定這個類的全限定名,這個全限定名就是說一個類的類名包含所有的包名,然後使用"/"代替"."。比如Object的全限定名是java.lang.Object。父類索引確定這個類的父類的全限定名,除了Object之外,所有的類都有父類,所以除了Object之外所有類的父類索引都不為0.介面索引集合儲存了implements語句後面按照從左到右的順序的介面。
類索引和父類索引都是一個索引,這個索引指向常量池中的CONSTANT_Class_info型別的常量。然後再CONSTANT_Class_info常量中的索引就可以找到常量池中型別為CONSTANT_Utf8_info的常量,而這個常量儲存著類的全限定名。
欄位表集合:
欄位表用來描述介面或類中宣告的變數。欄位包括類級變數和例項級變數,但不包括方法內變數。所謂的類級變數就是靜態變數,這個變數不屬於這個類的任何例項,可以不用定義類例項就可以使用;例項級變數不是靜態變數,是和類例項相關聯的,需要定義類例項才能使用。
那麼,宣告一個變數需要哪些資訊呢?有:欄位的作用域(public、private和protected修飾符)、是例項變數還是類變數(static修飾符)、可變性(final修飾符)、併發可見性(volatile修飾符)、是否可被序列化(transient修飾符)、欄位的資料型別(基本型別、物件、陣列)以及欄位名稱。包含的資訊有點多,不過不需要的可以不寫。這些資訊中,各個修飾符可以用布林值表示。而欄位叫什麼名字、欄位被定義為什麼型別資料都是無法固定的,只能用常量池中的常量來表示。下面是欄位表的格式:
型別 |
名稱 |
數量 |
U2 |
access_flags |
1 |
U2 |
name_index |
1 |
U2 |
descriptor_index |
1 |
U2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
其中的欄位修飾符access_flags,和類中的access_flags類似,對於欄位來說可以設定的標誌位及含義如下:
標誌名稱 |
標誌值 |
含義 |
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 |
0x1000 |
欄位是否是由編譯器自動產生的 |
ACC_ENUM |
0x4000 |
欄位是否是enum |
顯然,ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED只能選擇一個,ACC_FINAL和ACC_VOLATILE不能同時選擇。介面中的欄位必須有ACC_PUBLIC、ACC_STATIC和ACC_FINAL標誌,這是Java語言本身的規則決定的。
access_flags給出了欄位中所有可以用布林值表示的修飾符,剩下的資訊就是欄位的名字、變數型別等資訊。access_flags後面的是name_index和descriptor_index,前者是欄位名的常量池索引,後者是欄位描述符的常量池索引。name_index可以描述欄位的名字,descriptor_index可以描述欄位的資料型別。不過,對於方法的描述符來說就要複雜一些,因為一個方法除了返回值型別,還有引數型別,而且引數的個數還不確定。根據描述符規則,這些型別都使用一個大寫字母來表示,如下表:
標識字元 |
含義 |
標識字元 |
含義 |
B |
byte |
J |
long |
C |
char |
S |
short |
D |
double |
Z |
boolean |
F |
float |
V |
void |
I |
int |
L |
物件型別,如Ljava/lang/Object |
對於陣列型別,每一個維度將使用一個前置的“[”字元來描述。比如定義一個java.lang.String[][]型別的二維陣列,將記錄為"[[Ljava/lang/String",一個double陣列"double[]"將標記為"[D"。
當描述符用來描述方法時,按照先引數列表,後返回值的順序描述,引數列表按照引數的嚴格順序放在一組小括號"()"內。比如方法void inc()的描述符是:()V。方法java.lang.String toString()的描述符是:()Ljava/lang/String。方法int indexOf(char[] source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromIndex)的描述符是:([CII[CIII)I。
descriptor_info後面是屬性資訊,這會在後面屬性表集合中介紹。
方法表集合:
在欄位表集合中介紹了欄位的描述符和方法的描述符,對於理解方法表有很大幫助。class檔案儲存格式中對方法的描述和對欄位的描述幾乎相同,方法表的結構也和欄位表相同,這裡就不再列出。不過,方法表的訪問標誌和欄位的不同,列出如下:
標識名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
方法是否是public |
ACC_PRIVATE |
0x0002 |
方法是否是private |
ACC_PUBLICPROTECTED |
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 |
方法是否是由編譯器自動產生的 |
下面我們來一個例子
JavaCode
public class Test{
public int a;
private String b;
private int add(int arg1,int arg2){
return arg1+arg2;
}
}
二機制解析
Javap解析
C:\Users\GH\Desktop>javap -verbose Test.class
Classfile /C:/Users/GH/Desktop/Test.class
Last modified 2018-8-8; size 287 bytes
MD5 checksum 430dad91948d95b5532953a32356174c
Compiled from "Test.java"
public class Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#16 // java/lang/Object."<init>":()V
#2 = Class #17 // Test
#3 = Class #18 // java/lang/Object
#4 = Utf8 a
#5 = Utf8 I
#6 = Utf8 b
#7 = Utf8 Ljava/lang/String;
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 add
#13 = Utf8 (II)I
#14 = Utf8 SourceFile
#15 = Utf8 Test.java
#16 = NameAndType #8:#9 // "<init>":()V
#17 = Utf8 Test
#18 = Utf8 java/lang/Object
{
public int a;
descriptor: I
flags: ACC_PUBLIC
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
}
SourceFile: "Test.java"
C:\Users\GH\Desktop>