天天看點

深入了解JVM虛拟機:(四)類檔案結構(下)前言字段表集合方法表集合屬性表集合

前言

上一篇 深入了解JVM虛拟機:(三)類檔案結構(上) 中我們介紹了Java的類檔案結構,這一篇我們繼續來看Java其他的類檔案結構。

字段表集合

字段表用于描述接口或者類中聲明的變量。字段包括類級變量以及執行個體級變量,但不包括在方法内部聲明的局部變量。我們可以想一想在Java中描述一個字段可以包含什麼資訊?可以包括的資訊有:字段的作用域(private、protect、public修飾符)、是執行個體變量還是類變量(static修飾符)、可變性(final)、并發可見性(volatile修飾符,是否強制主從記憶體讀寫)、可否被序列化(transient修飾符)、字段資料類型(基本類型、對象、數組)、字段名稱。上述這些資訊中,各個修飾符都是布爾值,要麼有某個修飾符,要麼沒有,很合适使用标志位來表示。而字段叫什麼名字、字段被定義為什麼資料類型,這些都是無法固定的,隻能引用常量池中的常量來描述。

深入了解JVM虛拟機:(四)類檔案結構(下)前言字段表集合方法表集合屬性表集合

字段修飾符放在access_flags項目中,它與類中的access_flags項目是非常相似的,都是一個u2的資料類型,其中可以設定的标志位和含義見下圖

深入了解JVM虛拟機:(四)類檔案結構(下)前言字段表集合方法表集合屬性表集合

很明顯,在實際情況中,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三個标志做多隻能選擇其一,ACC_FINAL、ACC_VOLATILE不能同時選擇。接口之中的字段必須有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志,這些都是由Java本身的語言規則所決定的。

跟随access_flags标志的是兩項索引值:name_index和descriptor_index。它們都是對常量池的引用,分别代表着字段的簡單名稱和方法的描述符。現在需要解釋一下“簡單名稱”、“描述符”以及前面多次出現過的“全限定名”這三種特殊字元串的概念。

全限定名和簡單名稱很好了解,例如:“org/test/clazz/TestClass”是這個類的全限定名,僅僅是把類全名中的“.”替換成了“/”而已,為了使連續的多個全限定名之間不産生混淆,在使用時最後一般會加入一個“;”表示全限定名結束。簡單名稱是指沒有類型和參數修飾的方法或者字段名稱,這個類中的inc()方法和m字段的簡單名稱分為是“inc”和“m”。

相對于全限定名和簡單名稱來說,方法和字段的描述符就要複雜一些。描述符的作用是用來描述字段的資料類型、方法的參數清單(包括數量、類型以及順序)和傳回值。根據描述符規則,基本資料類型以及代表無傳回值的void類型都用一個大寫字元來表示,而對象類型則用字元L加對象的全限定名來表示。

深入了解JVM虛拟機:(四)類檔案結構(下)前言字段表集合方法表集合屬性表集合

對于數組類型,每一次元将使用一個前置的“[”字元來描述,如一個定義為“java.lang.String[][]”類型的二維數組,将被記錄為:“[[Ljava/lang/String”,一個整型數組“int[]”将被記錄為“[I”。

用描述符來描述方法時,按照先參數清單,後傳回值的順序描述,參數清單按照參數的嚴格順序放在一組小括号“()”之内。如方法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_index為止就結束了,不過在descriptor_index之後跟随着一個屬性表集合用于存儲一些額外的資訊,字段都可以在屬性表中描述零至多項的額外資訊。

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

方法表集合

Class檔案存儲格式中對方法的描述與對字段的描述幾乎采用了完全一緻的方式,方法表的結構如同字段表一樣,依次包括了通路标志(access_flag)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項。這些資料項目的含義也非常類似,僅在通路标志和屬性表集合的可選項中有所差別。

深入了解JVM虛拟機:(四)類檔案結構(下)前言字段表集合方法表集合屬性表集合

因為volatile關鍵字和transient關鍵字不能修飾方法,是以方法表的通路标志中沒有了ACC_VOLATILE标志和ACC_TRANSIENT标志。與之相對的,synchronized、native、strictfp和abstract關鍵字可以修飾方法,是以方法表的通路标志中增加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT标志。對于方法表,所有标志位及其取值見下圖。

深入了解JVM虛拟機:(四)類檔案結構(下)前言字段表集合方法表集合屬性表集合

在這裡,可能你會有疑問,方法的定義可以通過通路标志、名稱索引、描述符索引表達清楚,但方法裡面的代碼哪去了?方法裡的Java代碼,經過編譯器編譯成位元組碼指令後,存放在方法屬性表集合中一個名為“Code”的屬性裡面,屬性表作為Class檔案格式中最具有擴充性的一種資料項目,将在後續文章介紹。

與字段表集合相對應的,如果父類方法在子類中沒有被重寫,方法表集合中就不會出現來自父類的方法資訊。但同樣的,有可能會出現由編譯器自動添加的方法,最典型的便是類構造器“”方法和執行個體構造器“”方法。

在Java語言中,要重載一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特征簽名,特征簽名就是一個方法中各個參數在常量池中的字段符号引用的集合,也就是因為傳回值不會包含在特征簽名中,是以Java語言裡面是無法僅靠傳回值的不同來對一個已有方法進行重載的。但是在Class檔案格式中,特征簽名的範圍更大一些,隻要描述符不是完全一緻的兩個方法也可以共存。也就是說,如果兩個方法有相同的名稱和特征簽名,但傳回值不同,那麼也是可以合法共存于同一個Class檔案中的。

屬性表集合

屬性表在前面的講解中已經出現過多次,在Class檔案、字段表、方法表都可以攜帶自己的屬性表集合,以用于描述某些場景專有的資訊。

與Class檔案中其他的資料項目要求嚴格的順序、長度、和内容不同,屬性表集合的限制稍微放寬了一些,不再要求各個屬性表具有嚴格的順序,并且隻要不予已有屬性表名重複,任何人實作的編譯器都可以項屬性表中寫入自己定義的屬性資訊,Java虛拟機運作時會忽略掉它不認識的屬性。為了能正确解析Class檔案,《Java虛拟機規範》中預定義屬性已經增加到了21項,,具體見下表。

屬性名稱 使用位置 含義
Code 方法表 Java代碼編譯成的位元組碼指令
ConstantValue 字段表 final關鍵字定義的常量值
Deprecated 類、方法表、字段表 被聲明為deprecated的方法和字段
Exceptions 方法表 方法抛出的異常
EnclosingMethod 類檔案 僅當一個類為局部類或者匿名類時才能擁有這個屬性,這個屬性用于辨別這個類所在的外圍方法
InnerClasses 類檔案 内部類清單
LineNumberTable Code屬性 Java源碼的行号與位元組碼指令的對應關系
LocalVariableTable Code屬性 方法的局部變量描述
StackMapTable Code屬性 JDK1.6中增加的屬性,供新的類型檢查驗證器檢查和處理目标方法的局部變量和操作數棧所需要的類型是否比對
Signature 類、方法表、字段表 JDK1.5中新增的屬性,這個屬性用于支援泛型情況下的方法簽名,在Java語言中,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量或參數化類型,則Signature屬性會為它記錄泛型簽名資訊。由于Java的泛型采用擦除法實作,是為了避免類型資訊被擦除後導緻簽名混亂,需要這個屬性記錄泛型中的資訊
SourceFile 類檔案 記錄源檔案名稱
SourceDebugExtension 類檔案 JDK1.6中新增的屬性,SourceDebugExtension屬性用于存儲額外的調試資訊。譬如在進行JSP檔案調試時,無法通過Java堆棧來定位到JSP的行号,JSR-45規範為這些非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

1、Code屬性

Java程式方法體重的代碼經過Javac編譯器處理後,最終變為位元組碼指令存儲在Code屬性内。

2、Exception屬性

這裡的Exceptions屬性是在方法表與Code屬性平級的一項屬性,Exceptions屬性的作用是列舉出方法中可能抛出的 受檢查異常,也就是方法描述時在throws關鍵字後面列舉的異常。

3、LineNumberTable

LineNumberTable屬性用于描述Java源碼行号與位元組碼行号之間的對應關系。它不是運作時必須的屬性,但預設會生成到Class檔案中,可以在Javac中分别使用–g:none或-g:lines選項來取消或要求生成這項資訊。如果選擇不生成LineNumberTable屬性,對程式運作産生的最主要赢就是當抛出異常時,堆棧中将不會顯示出錯誤的行号,并且在調試程式的時候,也無法按照源碼行來設定斷點。

4、LocalVariableTable屬性

LocalVariableTable屬性用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關系,它也不是運作時必須的屬性,但預設會生成到Class檔案中,可以在Javac中分别使用-g:none或者-g:vars選項來取消或要求生成這項資訊。如果沒有生成這項屬性,最大的影響就是當其他人引用這個方法時,所有的參數名稱都将丢失,IDE将會使用諸如arg0、arg1之類的占位符代替原有的參數名,這對程式運作沒有影響,但是對代碼編寫帶來較大的不便,而且在調試期間無法根據參數名從上下文中擷取參數值。

5、SourceFile屬性

SourceFile屬性用于記錄生成這個Class檔案的源碼檔案名稱。這個屬性也是可選的,可以分别使用Javac的-g:none或-g:source選項來關閉或要求生成這項資訊。在Java中對于大多數的類來說,類名和檔案名是一緻的,但有一些特殊情況,例如内部類例外。

6、ConstantValue屬性

ConstantValue屬性的作用是通知虛拟機自動為靜态變量指派。隻有被static關鍵字修飾的變量才可以使用這項屬性。類似“int x=123”和“static int x=123”這樣的變量定義在Java程式中是非常常見的事情,但虛拟機對這兩種變量指派的方式和時刻都有所不同。對于非static類型的變量的指派是在執行個體構造器方法中進行的;而對于類變量,則有兩種方式可以選擇:在類構造器方法中或者使用ConstantValue屬性。目前Sun Javac編譯器的選擇是:如果同時使用final和static來修飾一個變量,并且這個變量的資料類型是基本類型或者String的話,就生成ConstantValue屬性來進行初始化,如果這個變量沒有被final修飾,或者并非基本類型及字元串,則将會選擇在方法中進行初始化。

7、InnerClasses屬性

InnerClasses屬性用于記錄内部類與宿主類之間的關聯。如果一個類中定義了内部類,那編譯器将會為它機器它所包含的内部類生成InnerClasses屬性。

8、BootstrapMethods屬性

BootstrapMethods屬性在JDK1.7釋出後增加到了Class檔案規範中,它是一個複雜的變長屬性,位于類檔案的屬性表中。這個屬性用于儲存invokedynamic指令引用的引導方法限定符。《Java虛拟機規範》規定,如果某個類檔案結構的常量池中曾經出現過CONSTANT_InvokeDynamic_info類型的常量,那麼這個類檔案的屬性表中必須存在一個明确的BootstrapMethods屬性,另外,即使CONSTANT_InvokeDynamic_info類型的常量在常量池中出現過多次,類檔案的屬性表中最多也隻能有一個BootstrapMethods屬性。

更多Java幹貨文章請關注我的個人微信公衆号:老宣與你聊Java

深入了解JVM虛拟機:(四)類檔案結構(下)前言字段表集合方法表集合屬性表集合