書接上一回,分享了Class文件的主要構成,同時也詳細分析了魔數、次版本號、主版本號、常量池集合、訪問標誌的構造,接下來咱們就繼續學習。java
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。編程
類索引(this_class)和父類索引(super_class)都是一個u2類型的數據,類索引用於肯定這個類的全限定名,父類索引用於肯定這個類的父類全限定名。因爲java語言不容許多重繼承,因此父類索引只有一個。數組
類索引和父類索引各自指向常量池中類型爲CONSTANT_Class_info的類描述符,再經過類描述符中的索引值找到常量池中類型爲CONSTANT_Utf8_info的字符串。再來看一下以前的Class文件例子:微信
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。學習
結合以前javap分析出來的常量池內容:this
#3 = Class #17 // OneMoreStudy #4 = Class #18 // java/lang/Object #17 = Utf8 OneMoreStudy #18 = Utf8 java/lang/Object
類索引爲0x0003,去常量池裏找索引爲3的類描述符,類描述符中的索引爲17,再去找索引爲17的字符串,就是「OneMoreStudy」。spa
父類索引爲0x0004,去常量池裏找索引爲4的類描述符,類描述符中的索引爲18,再去常量池裏找索引爲18的字符串,就是「java/lang/Object」。rest
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。code
接口索引集合(interface)是一組u2類型的數據的集合,因爲java語言容許實現多個接口,因此接口索引也有多個,它們按照implements語句後的接口順序從左到右依次排列在接口索引集合中。接口索引集合的第一項數據是接口集合計數值(interfaces_count),表示有多少接口索引。若是該類沒有實現任何接口,那麼該計數值爲0,後面的接口索引表不佔任何字節。以前的例子OneMoreStudy類沒有實現任何接口,因此接口集合計數值就是0,以下圖:對象
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
字段表(field_info)是用來描述接口或類中聲明的變量。包括類級變量(靜態變量)和實例級變量(成員變量),可是不包括在方法內部聲明的局部變量。具體結構以下表:
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
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 |
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
這裏提到了簡單名稱、描述符,和全限定名有什麼區別呢?稍微說一下。
簡單名稱是沒有類型和參數修飾的方法或字段名稱,好比OneMoreStudy類中的number字段和plusOne()方法的簡單名稱分別是「number」和「plusOne」。
全限定名是把類全名中的「.」替換成「/」就能夠了,好比java.lang.Object類的全限定名就是「java/lang/Object」。
描述符是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。基礎數據類型和無返回的void類型都有一個大寫字母表示,對象類型用字符L加對象的全限定名來表示,以下表:
標識字符 | 含義 |
---|---|
B | 基本類型byte |
C | 基本類型char |
D | 基本類型double |
F | 基本類型float |
I | 基本類型int |
J | 基本類型long |
S | 基本類型short |
Z | 基本類型boolean |
V | 特殊類型void |
L | 對象類型 如 Ljava/lang/Object |
對於數組類型,每一維度使用一個前置的「[」字符來描述,好比java.lang.Object[][]的二維數據,就是「[[Ljava/lang/Object」。在描述方法時,按照先參數列表,後返回值的順序描述,參數列表按照嚴格順序放在「()」值中,好比boolean equals(Object anObject),就是「(Ljava/lang/Object)B」。
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
再來看一下以前的Class文件例子:
OneMoreStudy類中只有一個字段number,因此字段計數值爲0x0001。字段number只被private修飾,沒有其餘修飾,因此字段的訪問標誌位爲0x0002。字段的簡單名稱索引是0x0005,去常量池中找索引爲5的字符串,爲「number」。字段的描述符索引爲0x0006,去常量池中找索引爲6的字符串,爲「I」,是基本類型int。如下是常量池相關內容:
#5 = Utf8 number #6 = Utf8 I
字段number的屬性計數值爲0x0000,也就是沒有須要額外描述的信息。
字段表集合中不會列出從父類或者父接口中繼承而來的字段,但有可能列出原版Java代碼中沒有的字段,好比在內部類中爲了保持對外部類的訪問性,會自動添加指向外部類實例的字段。
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
方法表的結構和字段表的是同樣的,也是依次包括了訪問標誌(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)和屬性表集合(attributes)。具體以下表:
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
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 |
ACC_FINAL | 0x0010 | 方法是否爲final |
ACC_SYNCHRONIZED | 0x0020 | 方法是否sychronized |
ACC_BRIDGE | 0x0040 | 方法是不是由編譯器產生的橋接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定參數 |
ACC_NATIVE | 0x0100 | 方法是否爲native |
ACC_ABSTRACT | 0x0400 | 方法是否爲abstract |
ACC_STRICT | 0x0800 | 方法是否爲strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否由編譯器自動產生 |
方法中的Java代碼,通過編譯器編程成字節碼指令後,放在方法屬性表集合中一個名爲「Code」的屬性裏,後面會有更多分享。
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
再來看一下以前的Class文件例子:
方法計算值爲0x0003,表示集合中有兩個方法(編譯器自動添加的無參構造方法和源碼中的plusOne方法)。第一個方法的訪問標誌是0x0001,表示只有ACC_PUBLIC標誌爲true。
名稱索引爲0x0007,在常量池中爲索引爲7的字符串爲「
#7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
屬性表(attribute_info)在前面的分享中出現了幾回,在Class文件、字段表、方法表均可以有本身的屬性表集合,用來描述某些場景下特有的信息。
屬性表不在要求具備嚴格的順序,而且只要不與已有的屬性名重複,任何人實現的編譯器均可以寫入本身定義的屬性信息,Java虛擬機在運行時會忽略掉它不認識的屬性。
我總結了一些比較常見的屬性,以下表:
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節碼指令 |
ConstantValue | 字段表 | final關鍵字定義的常量值 |
Exceptions | 方法表 | 方法拋出的異常 |
InnerClasses | 類文件 | 內部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節碼指定的對應關係 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
SourceFile | 類文件 | 記錄源文件名稱 |
對於每一個屬性,它的名稱都從常量池中引用一個CONSTANT_Utf8_info類型的常量,而屬性值的結構則是徹底自定義的,只須要用一個u4類型來講明屬性值所佔的位數就能夠了。具體結構以下:
類型 | 名稱 | 數量 | 含義 |
---|---|---|---|
u2 | attribute_name_index | 1 | 屬性名稱索引 |
u2 | attribute_length | 1 | 屬性值所佔的位數 |
u1 | info | attribute_length | 屬性值 |
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。
Class文件主要由魔數、次版本號、主版本號、常量池集合、訪問標誌、類索引、父類索引、接口索引集合、字段表集合、方法表集合和屬性表集合組成。隨着JDK版本的不斷升級,Class文件結構也在不斷更新,學習之路,永不止步。
歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。