目錄
一. 前言
二. class文件結構
2.1. 文件格式
2.2. 魔數與版本號
2.3. 常量池
2.4. 訪問標志
2.5.?類索引、父類索引和接口索引集合
2.6. 字段表集合
2.7.?方法表集合
2.8. 屬性表集合
2.8.1. Code 屬性表
2.8.2.?Exceptions 屬性
2.8.3.?LineNumberTable 屬性
2.8.4.?LocalVariableTable 屬性
2.8.5.?SourceFile 屬性
2.8.6.?ConstantValue 屬性
2.8.7.?InnerClass 屬性
2.8.8.?Deprecated 屬性和 Synthetic 屬性
2.8.9.?StackMapTable 屬性
2.8.10.?Signature 屬性
2.8.11.?BootstrapMethods 屬性
三. 實例分析
3.1. 示例源碼
3.2. 魔數
3.2. 版本號
3.3. 常量池
3.3.1. 常量池第 1 個常量
3.3.2. 常量池第 2 個常量
3.3.3.?常量池中第 3~4 常量
3.3.4.?常量池中第 5~14 及 17、18常量
3.3.5. 常量池第 15、16 個常量
3.4. 訪問標志
3.5. 類索引、父類索引
3.6. 字段表集合
3.7. 方法表集合
3.7.1.?第 1 個方法表
3.7.2. 第 2 個方法表
3.8.?Code 屬性表與 LineNumberTable 屬性表
3.9. SourceFile 屬性
一. 前言
? ? 我們平時在DOS界面中往往需要先運行 javac 命令,這個命令的直接結果就是產生相應的class文件,然后基于這個class文件才可以真正運行程序得到結果。當然,這是Java虛擬機的功勞,那么是不是Java虛擬機只能編譯 .java 的源文件呢?答案是否定的。時至今日,Java虛擬機已經實現了語言無關性的特點。而實現語言無關性的基礎是虛擬機和字節碼的存儲格式,Java虛擬機已經不和包括Java語言在內的任何語言綁定。它只與“class”文件這種特定的二進制文件相關聯。在class文件中包含了Java虛擬機指令集和符號表以及若干輔助信息。可以很容易想到Java(本質上不是Java語言本身的平臺無關性,而是其底層的Java虛擬機的平臺無關性使然。)的跨平臺,因為任何一門功能性語言都可以表示為能被Java虛擬機接受的有效的class文件。比如,除了Java虛擬機可以將Java源文件直接編譯為class文件外,使用JRuby等其他語言的編譯器一樣可以把程序代碼編譯成class文件,由此可見,Java虛擬機并不關心class文件是由何種語言編譯來的。
二. class文件結構
下面的圖是一字排開形式,都是平級關系,由于圖片排開太長,所以分行顯示,如下面這樣:
class文件格式采用一種類似C語言結構體的偽結構來存儲數據,這種偽結構只有兩種數據結構,即無符號數和表,解析class文件全是以這兩個數據結構為基礎。
無符號數:屬于基本的數據類型,由1字節、2字節、4字節、8字節分別用u1、u2、u3、u8表示,可以用來描述數字、索引引用、數量值或者UTF-8編碼構成字符串值。
表:是由多個無符號數或者表構成的復合數據結構,習慣以“_info”結尾class文件本質也可以看作一張表。
2.1. 文件格式
class文件格式嚴格按照下表的方式進行排列構成:
類型 | 名稱 | 含義 | 數量 |
---|---|---|---|
u4 | magic | 魔數 | 1 |
u2 | minor_version | 副版本號 | 1 |
u2 | major_version | 主版本號 | 1 |
u2 | constant_pool_count | 常量池計數器 | 1 |
cp_info | constant_pool | 常量池數據區(常量池表) | constant_pool_count -1 |
u2 | access_flags | 訪問標志(類的訪問控制權限) | 1 |
u2 | this_class | 類索引 | 1 |
u2 | super_class | 父類索引 | 1 |
u2 | interfaces_count | 接口計數器 | 1 |
u2 | interfaces | 接口信息數據區(接口表) | interfaces_count |
u2 | fields_count | 字段計數器 | 1 |
field_info | fields | 字段信息數據區(字段表) | fields_count |
u2 | methods_count | 方法計數器 | 1 |
method_info | methods | 方法信息數據區(方法表) | methods_count |
u2 | attributes_count | 附加屬性計數器 | 1 |
attribute_info | attributes | 附加屬性信息數據區(附加屬性表) | attributes_count |
2.2. 魔數與版本號
? ? 魔數固定為 0xCAFEBABE,4個字節。魔數的唯一作用在于確定這個class文件是否是Java虛擬機接受的class文件。如gif和jpeg等在文件頭中都存在魔術,使用魔術而不是使用擴展名是基于安全性考慮的——擴展名可以隨意被改變。
? ??緊接著魔數的4個字節是class文件版本號:版本號又分為副版本號和主版本號。其中前兩個字節用于表示副版本號,后兩個字節用于表示主版本號。版本號是隨著JDK版本的不同而表示不同的版本范圍的。如果class文件的版本號超過虛擬機版本,將被拒絕執行。
2.3. 常量池
? ? 在版本號之后,則是常量池入口,常量池在class文件中的作用非常重要,就是class文件中的資源倉庫,通常也是占用class文件空間最大的數據項目之一。由于常量池長度不是一定的,所以在常量池的入口處放置了一個兩個字節的常量池計數器來記錄常量池的容量有多大。
常量池計數器:記錄的是常量池數據區中的常量池項 cp_info 的數量。
1. 從1開始計數,第一個有用常量池項為1,所以常量池項的索引也是從1開始;
2. 至于索引為0的數據空間,class文件規范是如此定義的。
在常量池中主要存放字面量和符號引用。字面量比較接近Java語言層面的常量概念,比如文本字符串、聲明為final的常量值等。符號引用則主要包括三類常量:
1. 類和接口的全限定名;
2. 字段的名稱和描述符;
3. 方法的名稱和描述符。
// 常量池項 (cp_info) 的結構cp_info{tag:xxinfo[]:xx
}
tag:類型,用標記下面info數組的數據類型。
info[]:若干個字節構成數組。
常量 | 常量說明 | 項目 | 類型 | 描述 |
---|---|---|---|---|
CONSTANT_Utf8_info | 表示字符串常量的值。 (字面量型結構體) | tag標志 | u1 | 值為1 |
length | u2 | UTF-8編碼的字符串占用的字節數 | ||
bytes | u1 | 長度為length的UTF-8編碼的字符串 | ||
CONSTANT_Integer_info | 表示4字節(int)的數值常量。 (字面量型結構體) | tag標志 | u1 | 值為3 |
bytes | u4 | 按照高位在前存儲的int值 | ||
CONSTANT_Float_info | 表示4字節(float)的數值常量。 (字面量型結構體) | tag標志 | u1 | 值為4 |
bytes | u4 | 按照高位在前存儲的float值 | ||
CONSTANT_Long_info | 表示8字節(long)的數值常量。 (字面量型結構體) | tag標志 | u1 | 值為5 |
bytes | u8 | 按照高位在前存儲的long值 | ||
CONSTANT_Double_info | 表示8字節(double)的數值常量。 (字面量型結構體) | tag標志 | u1 | 值為6 |
bytes | u8 | 按照高位在前存儲的double值 | ||
CONSTANT_Class_info | 表示類或接口的完全限定名。 (引用型結構體) | tag標志 | u1 | 值為7 |
index | u2 | 指向全限定名常量項的索引 | ||
CONSTANT_String_info | 表示java.lang.String類型的常量對象。 (引用型結構體) | tag標志 | u1 | 值為8 |
index | u2 | 指向字符串字面量的索引 | ||
CONSTANT_Fieldref_info | 表示類中的字段。 (引用型結構體) | tag標志 | u1 | 值為9 |
index | u2 | 指向聲明字段的類或接口描述符CONSTANT_Class_info的索引項 | ||
index | u2 | 指向字段描述符CONSTANT_NameAndType的索引項 | ||
CONSTANT_Methodref_info | 表示類中的方法。 (引用型結構體) | tag標志 | u1 | 值為10 |
index | u2 | 指向聲明方法的類描述符CONSTANT_Class_info的索引項 | ||
index | u2 | 指向名稱及類型描述符CONSTANT_NameAndType的索引項 | ||
CONSTANT_InterfaceMethodref _info | 表示類中的方法。 (引用型結構體) | tag標志 | u1 | 值為11 |
index | u2 | 指向聲明方法的接口描述符CONSTANT_Class_info的索引項 | ||
index | u2 | 指向名稱及類型描述符CONSTANT_NameAndType的索引項 | ||
CONSTANT_NameAndType_info | 表示字段或方法的名稱和類型。 (引用型結構體) | tag標志 | u1 | 值為12 |
index | u2 | 指向該字段或方法名稱常量項的索引 | ||
index | u2 | 指向該字段或方法描述符常量項的索引 | ||
CONSTANT_MethodHandle_info | 表示方法句柄。 (引用型結構體) | tag標志 | u1 | 值為15 |
CONSTANT_MethodType_info | 表示方法類型。 (引用型結構體) | tag標志 | u1 | 值為16 |
CONSTANT_Dynamic_info | 表示一個動態計算常量。 (引用型結構體) | tag標志 | u1 | 值為17 |
CONSTANT_InvokeDynamic _info | 用于表示invokedynamic指令所使用到的引導方法(Bootstrap Method)、引導方法使用到動態調用名稱(Dynamic Invocation Name)、參數和請求返回類型 | tag標志 | u1 | 值為18 |
CONSTANT_Module_info | 表示一個模塊 | tag標志 | u1 | 值為19 |
CONSTANT_Package_info | 表示一個模塊中開放或者導出的包 | tag標志 | u1 | 值為20 |
細化了的常量池的結構會是類似下圖所示的樣子:
在JVM規范中,每個字段或者變量都有描述信息,描述信息的主要作用是 數據類型、方法參數列表、返回值類型等。基本參數類型和void類型都是用一個大寫的字符來表示,對象類型是通過一個大寫L加全類名表示,這么做的好處就是在保證JVM能讀懂class文件的情況下盡量的壓縮class文件體積。
基本數據類型表示:
字符 | 類型 | 字符 | 類型 |
---|---|---|---|
B | byte | C | char |
D | double | F | float |
I | int | J | long |
S | short | Z | boolean |
V | void |
對象類型:前面加一個L,然后把.改為/,String------>Ljava/lang/String;(后面有一個分號)。
數組類型:每一個維度都是用一個前置 [ 來表示,比如: int[] ------>[ I,String [][]------>[[Ljava.lang.String;(后面有一個分號)。
用描述符來描述方法:先參數列表,后返回值的格式,參數列表按照嚴格的順序放在()中
比如源碼 String getUserByIdAndName(int id, String name) 的方法描述符號為(I,Ljava/lang/String;)Ljava/lang/String;(后面有一個分號)。
2.4. 訪問標志
? ? 常量池之后的數據結構是訪問標志(access_flags),這個標志主要用于識別一些類或者接口層次的訪問信息,主要包括:這個Class是類還是接口、是否定義public、是否定義abstract類型;如果是類的話是否被聲明為final等。
類的標志訪問如下:
標志名稱 | 標志值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否為public |
ACC_FINAL | 0x0010 | 是否被聲明為final,只有類能設置 |
ACC_SUPER | 0x0020 | 是否允許使用invokespecial字節碼指令的新語義 |
ACC_INTERFACE | 0x0200 | 標識這是一個接口 |
ACC_ABSTRACT | 0x0400 | 是否為abstract類型,對于接口或者抽象類來說,此標志位為真,其他類型為假 |
ACC_SYNTHETIC | 0x1000 | 是否為這個類是由編譯器自動產生,并非由用戶代碼產生的 |
ACC_ANNOTATION | 0x2000 | 標識這是一個注解 |
ACC_ENUM | 0x4000 | 標識這是一個枚舉類 |
ACC_MODULE | 0x8000 | 標識這是一個模塊 |
字段的標志訪問如下:
方法的標志訪問如下:?
兩個訪問修飾符直接通過位運算來實現的。例如:現在有一個class文件訪問標識為00 21。那么實際它代表的是0x0021 = 0x0020 位運算 0x0001,即它代表這個class的訪問權限是ACC_PUBLIC和ACC_SUPER。
2.5.?類索引、父類索引和接口索引集合
? ? 類索引和父類索引都是一個兩個字節的數據,而接口索引是一組兩字節的數據集合,class文件中由這三項數據來確定該類型的繼承關系,類索引用于確定該類的全限定類名,父類索引用于確定該類父類的全限定類名。由于Java語言特性不支持多類繼承,所以父類索引只有一個,接口索引集合用于描述這類實現了哪些接口。
2.6. 字段表集合
? ? 字段表用于描述接口或者類中聲明的變量。字段包括類級變量和實例級變量,但是不包括方法內部聲明的局部變量(這些變量是存儲在Java虛擬機棧中的局部變量表中的)。自然,描述一個字段的信息包括:字段的作用域(public、protected、private)、實例變量與否(static)、可變性(final)、并發可見性(volatile)、可否被序列化(transient)、字段數據類型(基本數據類型、對象、數組)、字段名稱。
字段的信息也被存放在一張表中,其字段表包括三種類型:
1. u2類型訪問標志(access_flags),其訪問標志在access_flags中。
2. u2類型的name_index(字段的簡單名稱)。
3. u2類型的描述符(descriptor_index)。
字段表格式:
類型 | 名稱 | 數量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
2.7.?方法表集合
? ? JVM中堆方法表的描述與字段表是一致的,包括了:訪問標志、名稱索引、描述符索引、屬性表集合。方法表的結構與字段表是一致的,區別在于訪問標志的不同。在方法中不能用volatile和transient關鍵字修飾,所以這兩個標志不能用在方法表中。在方法中添加了字段不能使用的訪問標志,比如方法可以使用synchronized、native、strictfp、abstract關鍵字修飾,所以在方法表中就增加了相應的訪問標志。
注意:如果父類方法沒有在子類中重寫,那么在方法中不會自動出現來自父類的方法信息。同樣的,有可能添加編譯器自動增加的方法,比如方法。
方法表的結構如下:
2.8. 屬性表集合
? ? 前面的class文件、字段表和方法表都可以攜帶自己的屬性信息,這個信息用屬性表進行描述,用于描述某些場景專有的信息。在屬性表中沒有類似class文件的數據項目類型和順序的嚴格要求,只要新的屬性不與現有的屬性名重復,任何人都可以向屬性表中寫入自己定義的屬性信息。?
2.8.1. Code 屬性表
? ? Java程序方法體中的代碼經過 javac 編譯最終編譯成的字節碼指令就保存在Code屬性中。但是并非所有的方法表都必須存在這個屬性。Code屬性是class文件中最重要的一個屬性,如果把一個Java程序中的信息分為代碼(Code)和元數據(Metadata,包括類、字段、方法定義及其其他信息)兩部分,那么在整個class文件中,Code屬性用于描述代碼,所有其他的數據項目都用于描述元數據。
Code屬性表的結構:
類型 | 名稱 | 數量 | 說明 |
---|---|---|---|
u2 | attribute_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引,此常量固定為“Code”,代表該屬性的屬性名稱 |
u4 | attribute_length | 1 | 表示屬性長度 |
u2 | max_stack | 1 | 表示操作數棧深度的最大值 |
u2 | max_locals | 1 | 表示局部變量表所需的存儲空間,單位是變量槽(虛擬機為局部變量分配內存所使用的最小單位) |
u1 | code_length | 1 | 表示字節碼長度 |
u2 | code | code_length | 用于存儲字節碼指令。用來存儲java源文件編譯后生成的字節碼指令 |
u2 | exception_table_length | 1 | 表示異常處理表集合長度 |
exception_info | exception_table | exception_table_length | 表示異常處理表集合,異常處理表并不是Code必須部分! |
u2 | attributes_count | 1 | |
attribute_info | battributes | attribute_count |
2.8.2.?Exceptions 屬性
? ? Exceptions 屬性的作用是列舉出方法中可能拋出的受檢異常(Checked Exception),也就是描述throws 后的列舉的異常。
2.8.3.?LineNumberTable 屬性
? ? LineNumberTable屬性用于描述Java源碼行號與上傳字節碼行號之間的對應關系,并不是運行的必須屬性,但會默認生成到class文件中,主要作用就是拋出異常時會顯示行號,以及調試程序時可以根據源碼行號進行設置斷點。
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是一個 l類型為line_number_info的集合,包含start_pc和line_number兩個屬性都是占兩個字節,前者是字節碼行號,后者是java源碼行號。?
2.8.4.?LocalVariableTable 屬性
? ? 用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量的之間的關系。也不是必須的屬性。如果沒有這個屬性,產生的直接影響就是當別人引用這個方法的時候,所有的參數名稱都會丟失,IDE將會使用諸如 args0、args1 之類的參數進行顯示。自然,當調試程序的時候,顯示的參數名稱是不可知的。
2.8.5.?SourceFile 屬性
? ? 用于記錄這個class文件的源碼文件名稱,當類名和文件名不一致時拋出異常。如果不使用這個屬性,那么當拋出異常的時候,堆棧中將不會顯示出錯代碼所屬的文件名。
SourceFile屬性的結構為:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | sourcefile_index | 1 |
2.8.6.?ConstantValue 屬性
? ? ConstantValue 屬性作用是通知虛擬機自動為靜態變量賦值。要注意的是,只有被 static 關鍵字修飾的變量才可以使用這個屬性(類變量)。對于非類變量,初始化是在方法中進行的;對于類變量可以選擇兩種方式進行變量的初始化:一是在類構造器方法中使用;二是是ConstantValue屬性。目前Sun HotSpot的選擇原則是:如果一個變量同時使用static和final關鍵字修飾,并且這個變量是基本數據類型或者 java.lang.String 類型的話,就使用ConstantValue屬性進行初始化。如果沒有被 final 修飾或者并非是基本數據類型,那么將會選擇使用方法進行初始化。
2.8.7.?InnerClass 屬性
這個屬性主要用于記錄內部類與宿主類之間的關聯關系。
2.8.8.?Deprecated 屬性和 Synthetic 屬性
這兩個屬性都屬于標志類型的布爾屬性,只存在有沒有的區別。
Deprecated 屬性用于表示某個類、字段或者方法,已經被程序作者定為不再推薦使用,可以通過注解 @deprecated 實現。
Synthetic 屬性代表此字段并不是由Java源碼產生的,而是通過編譯器自行添加的。
2.8.9.?StackMapTable 屬性
該屬性的目的在于代替以前比較消耗性能的基于數據流分析的類型推導驗證器。
2.8.10.?Signature 屬性
? ? 這個屬性是專門用來記錄泛型類型的,因為在Java語言采用的是擦除法實現的泛型,在字節碼(Code屬性)中,泛型信息編譯之后會被擦除。擦除法的優點是能夠節省泛型所占的內存空間,缺點是在運行期間無法通過反射得到泛型信息,而Signature屬性則彌補了這一缺陷。現在的Java反射API已經能夠得到泛型信息,功勞就在于這個屬性。
2.8.11.?BootstrapMethods 屬性
? ? 這個屬性用于保存 invokedynamic 指令引用的引導方法限定符。該指令用于在運行時動態解析出調用點限定符所引用的方法,并執行該方法。
三. 實例分析
3.1. 示例源碼
示例代碼:
public class Test {private int m;public int inc(){return m + 1;}
}
編譯后的 class文件(使用16進制的方式來打開):
cafe babe 0000 0034 0013 0a00 0400 0f09
0003 0010 0700 1107 0012 0100 016d 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 0369 6e63
0100 0328 2949 0100 0a53 6f75 7263 6546
696c 6501 0009 5465 7374 2e6a 6176 610c
0007 0008 0c00 0500 0601 0004 5465 7374
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7400 2100 0300 0400 0000 0100 0200
0500 0600 0000 0200 0100 0700 0800 0100
0900 0000 1d00 0100 0100 0000 052a b700
01b1 0000 0001 000a 0000 0006 0001 0000
0001 0001 000b 000c 0001 0009 0000 001f
0002 0001 0000 0007 2ab4 0002 0460 ac00
0000 0100 0a00 0000 0600 0100 0000 0400
0100 0d00 0000 0200 0e
3.2. 魔數
ca fe ba be // 魔數值0xCAFEBABE
上述代碼可以看出 class文件的魔術值為0xCAFEBABE。
? ? MagicNumber?為每個class文件的頭4個字節,唯一作用就是用于判斷這個文件是否為一個能被虛擬機接受的 class文件。為什么使用魔數不使用擴展名進行判斷?(出于安全考慮,擴展名可以隨意修改,魔術值只要沒有被廣泛采用就不會引起混淆)。
3.2. 版本號
00 00 00 34 // 前兩位為次版本號,后兩位為主版本號
? ? 版本號為魔數后4個字節,前兩個字節為副版本號,后兩個字節為主版本號,0034十進制是52表示Java8,關于副版本號,在JDK1.2時短暫使用過,從JDK1.2到JDK12之前副版本號均為零,未被使用過。
3.3. 常量池
00 13 // 00 13表示常量中常量的數量為18
3.3.1. 常量池第 1 個常量
觀察例子中常量池第一個常量,它的標志位為0x0a,根據2.3 章節表可得知這個常量類型為CONSTANT_Methoddref_info,CONSTANT_Methoddref_info 類型結構為:
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
u1 | tag | 1 | 值為10 |
u2 | index | 1 | 指向方法的類描述符CONSTANT_Class_info索引項 |
u2 | index | 1 | 指向名稱及類型描述符CONTANT_Name_AndType索引項 |
結合字節碼分析,該類型一共占5個字節:
第一個占1個字節,用于區分索引類型;
第二個占2個字節,用于指向該類型中的CONSTANT_Class_info在常量池中的索引;
第三個占2個字節,用于指向該類型中的CONSTANT_NameAndType在常量池中的索引。
0a // 0a表示第 1 個常量類型為CONSTANT_Methoddref_info 長度為5個字節(包含標志位)
00 04 // 表示聲明方法的類的字段 CONSTANT_Class_info的索引項在常量池第4項常量
00 0f // 表示名稱及類型字段 CONSTANT_NameAndType的索引項在常量池的第 15 項常量
3.3.2. 常量池第 2 個常量
例子中常量池第二個常量,它的標志位為0x09,根據2.3 章節表可得知常量類型CONSTANT_Fieldref_info 的類型結構為:
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
u1 | tag | 1 | 值為9 |
u2 | index | 1 | 指向方法的類描述符CONSTANT_Class_info索引項 |
u2 | index | 1 | 指向名稱及類型描述符CONTANT_Name_AndType索引項 |
結合字節碼分析,該類型一共占5個字節:
第一個占1個字節,用于區分索引類型;
第二個占2個字節,用于指向該類型中的CONSTANT_Class_info在常量池中的索引;
第三個占2個字節,用于指向該類型中的CONSTANT_NameAndType在常量池中的索引。
09 // 表示第 2 個常量類型為 CONSTANT_Fieldref_info 長度為5個字節(包含標志位)
00 03 // 表示聲明方法的類的字段 CONSTANT_Class_info 的索引項在常量池第 3 項常量
00 10 // 表示字段CONSTANT_Name-AndType的索引在常量池的第 16 項
3.3.3.?常量池中第 3~4 常量
例子中第3個和第4個常量類型一樣,它的標志位都為0x07,根據2.3 章節表可得知常量類型為CONTANT_Class_info,類型結構為:
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
u1 | tag | 1 | 值為7 |
u2 | index | 1 | 指向全限定名常量的索引 |
結合字節碼分析,該類型一共占3個字節:
第一個占1個字節,用于區分索引類型;
第二個占2個字節,用于指向全限定名常量項的索引。
07 // 表示第 3 個常量類型為 CONTANT_Class_info 長度為 3 個字節(包含標志位)
00 11 // 表示全限定名存在常量池的第 17 項常量
07 // 表示第 4 個常量類型為 CONTANT_Class_info 長度為 3 個字節(包含標志位)
00 12 // 表示全限定名存在常量池的第 18 項常量
3.3.4.?常量池中第 5~14 及 17、18常量
第5到14以及17、18常量,它們標志位都是0x01,根據2.3 章節表可得知常量類型為CONTANT_Utf8_info,?類型結構為 CONTANT_Class_info,類型結構為:
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
u1 | tag | 1 | 值為1 |
u2 | length | 1 | UTF-8編碼的字符串占用了字節數 |
u1 | bytes | length | 長度為length的UTF-8編碼的字符串 |
結合字節碼分析:
第一個占1個字節,用于區分索引類型;
第二個占2個字節,用于描述該常量占用多少(length)字節數;
第三個占length個字節數,用于存儲utf-8編碼的字符串?。
01 // 表示第 5 個常量類型為 CONTANT_Utf8_info 長度為 4 個字節(包含標志位)
00 01 // 表示utf-8編碼占用了字節數為 1
6d // 6d 十進制為109 utf-8對應 m
01 // 表示第 6 個常量類型為 CONTANT_Utf8_info
00 01 // 表示長度為 1
49 // 49 十進制為 73 utf8對應 I
01 // 第 7 個常量為CONTANT_Utf8_info
00 06 // 常量長度為 6
3c 69 6e 69 74 3e // 一一對應 '<init>'
<!-- 下面utf全部簡略的描述 -->
01 // 第 8 個常量
00 03 // 長度為 3
28 29 56 // utf對應 ()V
01 // 第 9 個常量
00 04 // 長度為 4
43 6f 64 65 // Code
01 // 第 10 個變量
00 0f // 長度為 15
4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 // LineNumberTable
01 // 第 11 個變量
00 03 // 長度為3
69 6e 63 // inc
01 // 第 12 個變量
00 03 // 長度為3
28 29 49 // ()I
01 // 第 13 個變量
00 0a // 長度為10
53 6f 75 72 63 65 46 69 6c 65 // SourceFile
01 // 第 14 個變量
00 09 // 長度為9
54 65 73 74 2e 6a 61 76 61 // Test.java
······
01 // 第 17 個常量為 utf-8
00 04 // 長度為 4
54 65 73 74 // Test
01 // 第 18 個常量為 utf-8
00 10 // 長度為 16
6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 // java/lang/Object
3.3.5. 常量池第 15、16 個常量
第15、16個常量,它們的標志為都是0x0c,根據2.3 章節表可得知常量類型為CONSTANT_NameAndType_info,類型結構如下:
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
u1 | tag | 1 | 值為12 |
u2 | index | 1 | 指向該字段或方法名稱常量項索引 |
u2 | index | 1 | 指向指向該字段或方法描述常量項索引 |
結合字節碼分析,該類型一共占5個字節:
第一個占1個字節,用于區分索引類型;
第二個占2個字節,指向該字段或方法名稱常量項索引;
第三個占2個字節,指向指向該字段或方法描述常量項索引。
0c // 表示第 15 個常量量類型為 CONSTANT_NameAndType_info 長度為5個字節(包含標志位)
00 07 // 表示該字段的表示的字段或方法的名稱為常量池的第7個常量
00 08 // 表示該字段表示的字段或方法的描述為位常量池的第8個常量
0c // 表示第 16 個常量量類型為 CONSTANT_NameAndType_info 長度為5個字節(包含標志位)
00 05 // 表示該字段的表示的字段或方法的名稱為常量池的第5個常量
00 06 // 表示該字段表示的字段或方法的描述為位常量池的第6個常量
到這常量池已經全部解析完成:?可以使用 javap -verbose 命令輸出常量表,對常量表進行驗證:
3.4. 訪問標志
0021 根據2.3 章節表可得知該類使用 invokespecial 字節碼指令的新語義,且被public修飾。
00 21 // access_flags 標志位占用 2個字節位,0021表示被public修飾符修飾,且使用invokespecial字節碼指令的新語義
3.5. 類索引、父類索引
00 03 // this_class 類索引,占用 2 個字節,表示在類名稱存放在常量池第 3 項常量中
00 04 // super_class 父類索引,占用 2 個字節,表示父類名稱存放在常量池第 4 項常量中
00 // interface_count 占用 1 個字節,表示實現接口的數量為0
00 // interfaces 接口為空
3.6. 字段表集合
<!-- 字段表集合 -->
00 01 // fields_count 表示只有 1 個字段表數據 占用 2 個字節
00 02 // access_flags 表示字段修飾符為private 占用 2 個字節
00 05 // name_index 表示字段名稱存放在常量池第 5 項常量 占用 2 個字節
00 06 // descriptor_index 表示字段方法的描述存放在常量池的第 6 項常量 占用 2 個字節
00 // attributes_count 屬性表數量為0 占用 2 個字節
00 // attributes 表示屬性表集合為空
3.7. 方法表集合
<!-- 方法表集合 -->
00 02 // methods_count 表示表示方法表集合中包含 2 個方法
3.7.1.?第 1 個方法表
<!-- 第一個方法表 -->
00 01 // access_flags 表示方法修飾符為public 占用 2 個字節
00 07 // name_index 方法名稱存放在常量池第 7 個 占用 2 個字節
00 08 // descriptor_index 表示方法描述存放在常量池第 8 個 占用 2 個字節
00 01 // attributes_count 表示此方法屬性表集合中有 1 項屬性 占用 2 個字節
<!-- attribute_info (code)-->
00 09 // attribute_name_index 表示屬性存放在常量池第 9 項 占用 2 個字節
00 00 00 1d // attribute_lenth 表示該屬性的長度 占用 4 個字節
00 01 // max_stack 操作數棧的最大深度 占用 2 個字節
00 01 // max_locals 局部變量表所需的存儲空間 占用兩個字節
00 00 00 05 // code_length 表示字節碼長度,占用 4 個字節
2a b7 00 01 b1 // code 用于存儲編譯后的字節碼指令 占用code_length 個字節00 00 // exception_table_length 異常表長度 占用 2 個字節00 01 // code屬性中的 attributes_count 占用兩個字節
00 0a // attribute_name_index 屬性名稱在常量池的第10項常量
00 00 00 06 // LineNumber類型的 attribute_length 屬性長度 占用 4 個字節
00 01 // line_number_table_length 表示有幾個line_number_info類型的數據 占用 2 個字節
<!-- line_number_info -->
00 00 // start_pc 字節碼行號
00 01 // line_number java源碼行號
3.7.2. 第 2 個方法表
<!-- 第二個方法表 -->
00 01 // access_flags 表示方法修飾符為public 占用 2 個字節
00 0b // name_index 方法名稱存放在常量池第 11 個 占用 2 個字節
00 0c // descriptor_index 表示方法描述存放在常量池第 12 個 占用 2 個字節
00 01 // attributes_count 表示此方法屬性表集合中有 1 項屬性 占用 2 個字節
<!-- attribute_info (code) -->
00 09 // attribute_name_index 表示屬性存放在常量池第 9 項 占用 2 個字節
00 00 00 1f // attribute_lenth 表示該屬性的長度 占用 4 個字節
00 02 // max_stack 操作數棧的最大深度 占用 2 個字節
00 01 // max_locals 局部變量表所需的存儲空間 占用 2 個字節
00 00 00 07 // code_length 表示字節碼的長度 占用 4 個字節
2a b4 00 02 04 60 ac// code 用于存儲編譯后的字節碼指令 占用code_length個字節
00 00 // exception_table_length 異常表長度 占用 2 個字節
00 01 // cod屬性中的 attributes_count 表示有幾個屬性 占用兩個字節
00 0a // attribute_name_index 屬性名在常量池中的索引 表示是常量池中第 10 個索引 占用 2 個字節
00 00 00 06 // LineNumber類型的 attribute_length 屬性長度占用 4 個字節
00 01 // line_number_table_length 表示有幾個 line_number_info類型的數據 站喲 兩個字節
<!-- line_number_info -->
00 00 // start_pc 字節碼行號
00 04 // line_number java源碼行號
3.8.?Code 屬性表與 LineNumberTable 屬性表
<!-- 第一個方法 attribute_info (code)-->
00 09 // attribute_name_index 表示屬性存放在常量池第 9 項 占用 2 個字節
00 00 00 1d // attribute_lenth 表示該屬性的長度 占用 4 個字節
00 01 // max_stack 操作數棧的最大深度 占用 2 個字節
00 01 // max_locals 局部變量表所需的存儲空間 占用兩個字節
00 00 00 05 // code_length 表示字節碼長度,占用 4 個字節
2a b7 00 01 b1 // code 用于存儲編譯后的字節碼指令 占用code_length 個字節00 00 // exception_table_length 異常表長度 占用 2 個字節00 01 // code屬性中的 attributes_count 占用兩個字節
00 0a // attribute_name_index 屬性名稱在常量池的第10項常量
00 00 00 06 // LineNumber類型的 attribute_length 屬性長度 占用 4 個字節
00 01 // line_number_table_length 表示有幾個line_number_info類型的數據 占用 2 個字節
<!-- line_number_info -->
00 00 // start_pc 字節碼行號
00 01 // line_number java源碼行號<!--第二個方法 attribute_info (code) -->
00 09 // attribute_name_index 表示屬性存放在常量池第 9 項 占用 2 個字節
00 00 00 1f // attribute_lenth 表示該屬性的長度 占用 4 個字節
00 02 // max_stack 操作數棧的最大深度 占用 2 個字節
00 01 // max_locals 局部變量表所需的存儲空間 占用 2 個字節
00 00 00 07 // code_length 表示字節碼的長度 占用 4 個字節
2a b4 00 02 04 60 ac// code 用于存儲編譯后的字節碼指令 占用code_length個字節
00 00 // exception_table_length 異常表長度 占用 2 個字節
00 01 // cod屬性中的 attributes_count 表示有幾個屬性 占用兩個字節
00 0a // attribute_name_index 屬性名在常量池中的索引 表示是常量池中第 10 個索引 占用 2 個字節
00 00 00 06 // LineNumber類型的 attribute_length 屬性長度占用 4 個字節
00 01 // line_number_table_length 表示有幾個 line_number_info類型的數據 站喲 兩個字節
<!-- line_number_info -->
00 00 // start_pc 字節碼行號
00 04 // line_number java源碼行號
3.9. SourceFile 屬性
<!-- 屬性表 -->
00 01 // attributes_count 表示有幾個屬性 占用兩個字符
<!-- attribute_info -->
00 0d // attribute_name_index 屬性名稱在常量池中的索引 占用 2 個字節
00 00 00 02 // attribute_length 屬性長度 占用 4 個字節
00 0e // sourcefile_index 資源文件名稱在常量池中的索引
到此為止,class文件全部解析完成,讀者也可以使用該Java源代碼自己進行編譯按照該思路進行分析,相信你們一定也會有所收獲!