無關(guān)性的基石
實現(xiàn)語言無關(guān)性的基礎(chǔ)仍然是虛擬機和字節(jié)碼存儲格式。Java虛擬機不和包括Java在內(nèi)的任何語言綁定,它只與“Class文件”這種特定的二進制文件格式所關(guān)聯(lián),Class文件中包含了Java虛擬機指令集和符號表以及若干其他輔助信息。基于安全方面的考慮,Java虛擬機規(guī)范要求在Class文件中使用許多強制性的語法和結(jié)構(gòu)化約束,但任一門功能性語言都可以表示為一個能被Java虛擬機所接受的有效的Class文件。作為一個通用的、機器無關(guān)的執(zhí)行平臺,任何其他語言的實現(xiàn)者都可以將Java虛擬機作為語言的產(chǎn)品交付媒介。例如,使用Java編譯器可以把Java代碼編譯為存儲字節(jié)碼的Class文件,使用JRuby等其他語言的編譯器一樣可以把程序代碼編譯成Class文件,虛擬機并不關(guān)心Class的來源是何種語言.

Java語言中的各種變量、關(guān)鍵字和運算符號的語義最終都是由多條字節(jié)碼命令組合而成的,因此字節(jié)碼命令所能提供的語義描述能力肯定會比Java語言本身更加強大。因此,有一些Java語言本身無法有效支持的語言特性不代表字節(jié)碼本身無法有效支持,這也為其他語言實現(xiàn)一些有別于Java的語言特性提供了基礎(chǔ)。
Class類文件的結(jié)構(gòu)
任何一個Class文件都對應(yīng)著唯一一個類或接口的定義信息,但反過來說,類或接口并不一定都得定義在文件里(譬如類或接口也可以通過類加載器直接生成)。這里只是通俗地將任意一個有效的類或接口所應(yīng)當滿足的格式稱為“Class文件格式”,實際上它并不一定以磁盤文件的形式存在。
Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進制流,各個數(shù)據(jù)項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內(nèi)容幾乎全部是程序運行的必要數(shù)據(jù),沒有空隙存在。當遇到需要占用8位字節(jié)以上空間的數(shù)據(jù)項時,則會按照高位在前的方式分割成若干個8位字節(jié)進行存儲。
無符號數(shù)屬于基本的數(shù)據(jù)類型,以u1、u2、u4、u8來分別代表1個字節(jié)、2個字節(jié)、4個字節(jié)和8個字節(jié)的無符號數(shù),無符號數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。
表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構(gòu)成的復合數(shù)據(jù)類型,所有表都習慣性地以“_info”結(jié)尾。表用于描述有層次關(guān)系的復合結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)上就是一張表;

魔數(shù)與Class文件的版本
每個Class文件的頭4個字節(jié)稱為魔數(shù)(Magic Number),它的唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件。很多文件存儲標準中都使用魔數(shù)來進行身份識別,譬如圖片格式,如gif或者jpeg等在文件頭中都存有魔數(shù)。使用魔數(shù)而不是擴展名來進行識別主要是基于安全方面的考慮,因為文件擴展名可以隨意地改動。
常量池
由于常量池中常量的數(shù)量是不固定的,所以在常量池的入口需要放置一項u2類型的數(shù)據(jù),代表常量池容量計數(shù)值(constant_pool_count)。與Java中語言習慣不一樣的是,這個容量計數(shù)是從1而不是0開始的
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。字面量比較接近于Java語言層面的常量概念,如文本字符串、聲明為final的常量值等。而符號引用則屬于編譯原理方面的概念,包括了下面三類常量:
- 類和接口的全限定名(Fully Qualified Name)
- 全限定名是在整個JVM中的絕對名稱,可以表示Class文件結(jié)構(gòu)中的類或接口的名稱。
- 字段的名稱和字段描述符(Descriptor)
- 字段名稱及其類型
- 方法的名稱和方法描述符
- 分為參數(shù)描述符和返回值描述符
Java代碼在進行Javac編譯的時候,并不像C和C++那樣有“連接”這一步驟,而是在虛擬機加載Class文件的時候進行動態(tài)連接。當虛擬機運行時,需要從常量池獲得對應(yīng)的符號引用,再在類創(chuàng)建時或運行時解析、翻譯到具體的內(nèi)存地址之中.常量池中每一項常量都是一個表,在JDK 1.7之前共有11種結(jié)構(gòu)各不相同的表結(jié)構(gòu)數(shù)據(jù),在JDK 1.7中為了更好地支持動態(tài)語言調(diào)用,又額外增加了3種(CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info)

自動生成的常量的確沒有在Java代碼里面直接出現(xiàn)過,但它們會被后面即將講到的字段表(field_info)、方法表(method_info)、屬性表(attribute_info)引用到,它們會用來描述一些不方便使用“固定字節(jié)”進行表達的內(nèi)容。


訪問標志(access_flag)
在常量池結(jié)束之后,緊接著的兩個字節(jié)代表訪問標志(access_flags),這個標志用于識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義為public類型;是否定義為abstract類型;如果是類的話,是否被聲明為final等。

access_flags中一共有16個標志位可以使用,當前只定義了其中8個(Java虛擬機規(guī)范中,只定義了開頭5種標志。JDK 1.5中增加了后面3種。這些標志為在JSR-202規(guī)范中聲明,是對《Java虛擬機規(guī)范(第2版)》的補充),沒有使用到的標志位要求一律為0
類索引、父類索引與接口索引集合
類索引(this_class)和父類索引(super_class)都是一個u2類型的數(shù)據(jù),而接口索引集合(interfaces)是一組u2類型的數(shù)據(jù)的集合 ,Class文件中由這三項數(shù)據(jù)來確定這個類的繼承關(guān)系。類索引用于確定這個類的全限定名,父類索引用于確定這個類的父類的全限定名。由于Java語言不允許多重繼承,所以父類索引只有一個,除java.lang.Object之外,所有的Java類都有父類,因此除了java.lang.Object外,所有Java類的父類索引都不為0。接口索引集合就用來描述這個類實現(xiàn)了哪些接口,這些被實現(xiàn)的接口將按implements語句(如果這個類本身是一個接口,則應(yīng)當是extends語句)后的接口順序從左到右排列在接口索引集合中。
類索引、父類索引和接口索引集合都按順序排列在訪問標志之后,類索引和父類索引用兩個u2類型的索引值表示,它們各自指向一個類型為CONSTANT_Class_info的類描述符常量,通過CONSTANT_Class_info類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。

對于接口索引集合,入口的第一項——u2類型的數(shù)據(jù)為接口計數(shù)器(interfaces_count),表示索引表的容量。如果該類沒有實現(xiàn)任何接口,則該計數(shù)器值為0,后面接口的索引表不再占用任何字節(jié)。

從偏移地址0x000000F1開始的3個u2類型的值分別為0x0001、0x0003、0x0000,也就是類索引為1,父類索引為3,接口索引集合大小為0,查詢前面代碼清單6-2中javap命令計算出來的常量池,找出對應(yīng)的類和父類的常量
結(jié)果代碼:
const#1=class#2;//org/fenixsoft/clazz/TestClass
const#2=Asciz org/fenixsoft/clazz/TestClass;
const#3=class#4;//java/lang/Object
const#4=Asciz java/lang/Object;
字段表集合
字段表(field_info)用于描述接口或者類中聲明的變量。
字段(field)包括類級變量以及實例級變量,但不包括在方法內(nèi)部聲明的局部變量。
在Java中描述一個字段可以包含的信息:
- 字段的作用域(public、private、protected修飾符)
- 實例變量還是類變量(static修飾符)
- 可變性(final)
- 并發(fā)可見性(volatile修飾符,是否強制從主內(nèi)存讀寫)
- 可否被序列化(transient修飾符)
- 字段數(shù)據(jù)類型(基本類型、對象、數(shù)組)
- 字段名稱
上述這些信息中,各個修飾符都是布爾值,要么有某個修飾符,要么沒有,很適合使用標志位來表示。而字段叫什么名字、字段被定義為什么數(shù)據(jù)類型,這些都是無法固定的,只能引用常量池中的常量來描述
-
上下左右順序進行排序的
字段表結(jié)構(gòu)
字段修飾符放在access_flags項目中,它與類中的access_flags項目是非常類似的,都是一個u2的數(shù)據(jù)類型

很明顯,在實際情況中,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三個標志最多只能選擇其一,ACC_FINAL、ACC_VOLATILE不能同時選擇。接口之中的字段必須有ACC_PUBLIC、ACC_STATIC、ACC_FINAL標志,這些都是由Java本身的語言規(guī)則所決定的
跟隨access_flags標志的是兩項索引值:name_index和descriptor_index。它們都是對常量池的引用,分別代表著字段的簡單名稱以及字段和方法的描述符。現(xiàn)在需要解釋一下“簡單名稱”、“描述符”以及前面出現(xiàn)過多次的“全限定名”這三種特殊字符串的概念。
相對于全限定名和簡單名稱來說,方法和字段的描述符就要復雜一些。描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示

對于數(shù)組類型,每一維度將使用一個前置的“[”字符來描述,如一個定義為“java.lang.String[][]”類型的二維數(shù)組,將被記錄為:“[[Ljava/lang/String;”,一個整型數(shù)組“int[]”將被記錄為“[I”。
簡單的Java代碼
package org.fenixsoft.clazz;
public class TestClass{
private int m;
public int inc(){
return m+1;
}
}
對于上述代碼中的TestClass文件來說,,字段表集合從地址0x000000F8開始,第一個u2類型的數(shù)據(jù)為容量計數(shù)器fields_count,如下表所示,其值為0x0001,說明這個類只有一個字段表數(shù)據(jù)。接下來緊跟著容量計數(shù)器的是access_flags標志,值為0x0002,代表private修飾符的ACC_PRIVATE標志位為真(ACC_PRIVATE標志的值為0x0002),其他修飾符為假。代表字段名稱的name_index的值為0x0005,從常量表中可查得第5項常量是CONSTANT_Utf8_info類型的字符串,其值為“m”,代表字段描述符的descriptor_index的值為0x0006,指向常量池的字符串“I”,根據(jù)這些信息,我們可以推斷出原代碼定義的字段為:“private int m;”。

字段表都包含的固定數(shù)據(jù)項目到descriptor_index為止就結(jié)束了,不過在descriptor_index之后跟隨著一個屬性表集合用于存儲一些額外的信息,字段都可以在屬性表中描述零至多項的額外信息。對于本例中的字段m,它的屬性表計數(shù)器為0,也就是沒有需要額外描述的信息,但是,如果將字段m的聲明改為“final static int m=123;”,那就可能會存在一項名稱ConstantValue的屬性,其值指向常量123。
方法表集合
Class文件存儲格式中對方法的描述與對字段的描述幾乎采用了完全一致的方式,方法表的結(jié)構(gòu)如同字段表一樣,依次包括了訪問標志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項.這些數(shù)據(jù)項目的含義也非常類似,僅在訪問標志和屬性表集合的可選項中有所區(qū)別。

因為volatile關(guān)鍵字和transient關(guān)鍵字不能修飾方法,所以方法表的訪問標志中沒有了ACC_VOLATILE標志和ACC_TRANSIENT標志。與之相對的,synchronized、native、strictfp和abstract關(guān)鍵字可以修飾方法,所以方法表的訪問標志中增加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP和ACC_ABSTRACT標志

方法里的Java代碼,經(jīng)過編譯器編譯成字節(jié)碼指令后,存放在方法屬性表集合中一個名為“Code”的屬性里面,屬性表作為Class文件格式中最具擴展性的一種數(shù)據(jù)項目
以上述的簡單Java代碼為例,方法集合的入口地址為:0x00000101,第一個u2類型的數(shù)據(jù)(即是計數(shù)器容量)的值為0x0002,代表集合中有兩個方法(這兩個方法為編譯器添加的實例構(gòu)造器<init>和源碼中的方法inc())。第一個方法的訪問標志值為0x001,也就是只有ACC_PUBLIC標志為真,名稱索引值為0x0007,查代碼清單的常量池得方法名為“<init>”,描述符索引值為0x0008,對應(yīng)常量為“()V”,屬性表計數(shù)器attributes_count的值為0x0001就表示此方法的屬性表集合有一項屬性,屬性名稱索引為0x0009,對應(yīng)常量為“Code”,說明此屬性是方法的字節(jié)碼描述。

與字段表集合相對應(yīng)的,如果父類方法在子類中沒有被重寫(Override),方法表集合中就不會出現(xiàn)來自父類的方法信息。但同樣的,有可能會出現(xiàn)由編譯器自動添加的方法,最典型的便是類構(gòu)造器“<clinit>”方法和實例構(gòu)造器“<init>”方法。
在Java語言中,要重載(Overload)一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特征簽名[1],特征簽名就是一個方法中各個參數(shù)在常量池中的字段符號引用的集合,也就是因為返回值不會包含在特征簽名中,因此Java語言里面是無法僅僅依靠返回值的不同來對一個已有方法進行重載的。但是在Class文件格式中,特征簽名的范圍更大一些,只要描述符不是完全一致的兩個方法也可以共存。也就是說,如果兩個方法有相同的名稱和特征簽名,但返回值不同,那么也是可以合法共存于同一個Class文件中的。
[1]Java代碼的方法特征簽名只包括了方法名稱、參數(shù)順序及參數(shù)類型,而字節(jié)碼的特征簽名還包括方法返回值以及受查異常表
屬性表集合
屬性表(attribute_info):在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,以用于描述某些場景專有的信息。
與Class文件中其他的數(shù)據(jù)項目要求嚴格的順序,長度和內(nèi)容不同,屬性表集合的限制稍微寬松一些.不再要求各個屬性具有嚴格順序,并且只要不與已有的屬性名重復,任何人實現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱约憾x的屬性信息,Java虛擬機運行時會忽略它不認識的屬性.為了能夠正確解析Class文件,《Java虛擬機規(guī)范(第2版)》中預(yù)定義了9項虛擬機實現(xiàn)應(yīng)當能識別的屬性,而在最新的《Java虛擬機規(guī)范(Java SE 7)》版中,預(yù)定義屬性已經(jīng)增加到21項.


對于每一個屬性,它的名稱需要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性值的結(jié)構(gòu)則是完全自定義的,只需要通過一個u4的長度屬性去說明屬性值所占用的位數(shù)即可。一個符合規(guī)則的屬性表應(yīng)該滿足表6-14中所定義的結(jié)構(gòu)。

1.Code屬性
Java程序方法體中的代碼經(jīng)過Javac編譯器處理后,最終變?yōu)樽止?jié)碼指令都存在Code屬性內(nèi).Code屬性出現(xiàn)在方法表的屬性集合之中,但并非所有的方法表都必須有這個屬性,譬如接口或者抽象類中的方法就不存在Code屬性,如果方法表中有Code屬性,那么它的結(jié)構(gòu)將如下圖所示

attribute_name_index是一項指向CONSTANT_Utf8_info型常量的索引,常量值固定為“Code”,它代表了該屬性的屬性名稱,attribute_length指示了屬性值的長度,由于屬性名稱索引與屬性長度一共為6字節(jié),所以屬性值的長度固定為整個屬性表長度減去6個字節(jié)。
max_stack代表了操作數(shù)棧(Operand Stacks)深度的最大值。在方法執(zhí)行的任意時刻,操作數(shù)棧都不會超過這個深度。虛擬機運行的時候需要根據(jù)這個值來分配棧幀(StackFrame)中的操作棧深度。
max_locals代表了局部變量表所需的存儲空間。在這里,max_locals的單位是Slot,Slot是虛擬機為局部變量分配內(nèi)存所使用的最小單位。對于byte、char、float、int、short、boolean和returnAddress等長度不超過32位的數(shù)據(jù)類型,每個局部變量占用1個Slot,而double和long這兩種64位的數(shù)據(jù)類型則需要兩個Slot來存放。*方法參數(shù)(包括實例方法中的隱藏參數(shù)“this”)、顯式異常處理器的參數(shù)(Exception Handler Parameter,就是try-catch語句中catch塊所定義的異常)、方法體中定義的局部變量都需要使用局部變量表來存放。另外,并不是在方法用到了多少個局部變量,就把這些局部變量所占Slot之和作為max_locals的值,原因是局部變量表中的Slot可以重用,當代碼執(zhí)行超出一個局部變量的作用域時,這個局部變量所占的Slot可以被其他局部變量所使用,Javac編譯器會根據(jù)變量的作用域來分配Slot給各個變量使用,然后計算出max_locals的大小。
code_length和code用來存儲Java源程序編譯后生成的字節(jié)碼指令。code_length代表字節(jié)碼長度,code是用于存儲字節(jié)碼指令的一系列字節(jié)流。既然叫字節(jié)碼指令,那么每個指令就是一個u1類型的單字節(jié),當虛擬機讀取到code中的一個字節(jié)碼時,就可以對應(yīng)找出這個字節(jié)代表的是什么指令,并且可以知道這條指令后面是否需要跟隨參數(shù),以及參數(shù)應(yīng)當如何理解。我們知道一個u1數(shù)據(jù)類型的取值范圍為0x00~0xFF,對應(yīng)十進制的0~255,也就是一共可以表達256條指令,目前,Java虛擬機規(guī)范已經(jīng)定義了其中約200條編碼值對應(yīng)的指令含義
關(guān)于code_length,有一件值得注意的事情,雖然它是一個u4類型的長度值,理論上最大值可以達到232-1,但是虛擬機規(guī)范中明確限制了一個方法不允許超過65535條字節(jié)碼指令,即它實際只使用了u2的長度,如果超過這個限制,Javac編譯器也會拒絕編譯。一般來講,編寫Java代碼時只要不是刻意去編寫一個超長的方法來為難編譯器,是不太可能超過這個最大值的限制。但是,某些特殊情況,例如在編譯一個很復雜的JSP文件時,某些JSP編譯器會把JSP內(nèi)容和頁面輸出的信息歸并于一個方法之中,就可能因為方法生成字節(jié)碼超長的原因而導致編譯失敗
Code屬性是Class文件中重要的一個屬性,如果把一個Java程序中的信息分為代碼(Code,方法體里面的Java代碼)和元數(shù)據(jù)(Metadata,包括類,字段,方法定義以及其他信息)兩部分,那么在整個Class文件中,Code屬性用于描述代碼,所有的其他的數(shù)據(jù)項目都用于描述元數(shù)據(jù).
在任何實例方法里面,都可以通過“this”關(guān)鍵字訪問到此方法所屬的對象。這個訪問機制對Java程序的編寫很重要,而它的實現(xiàn)卻非常簡單,僅僅是通過Javac編譯器編譯的時候把對this關(guān)鍵字的訪問轉(zhuǎn)變?yōu)閷σ粋€普通方法參數(shù)的訪問,然后在虛擬機調(diào)用實例方法時自動傳入此參數(shù)而已--也就是說實際上是傳入了一個參數(shù)的,但是省略了,使用this.可以調(diào)用這個參數(shù)m。因此在實例方法的局部變量表中至少會存在一個指向當前對象實例的局部變量,局部變量表中也會預(yù)留出第一個Slot位來存放對象實例的引用,方法參數(shù)值從1開始計算。這個處理只對實例方法有效,如果代碼清單中的inc()方法聲明為static,那Args_size就不會等于1而是等于0了
在字節(jié)碼指令之后的是這個方法的顯示異常處理表(下文稱異常表)集合,異常表對于Code屬性來說并不是必須存在的
異常表的格式如下圖所示,它包含四個字段,這些字段的含義為:如果當字節(jié)碼在第start_pc行到第end_pc行之間(不含第end_pc行)出現(xiàn)了類型為catch_type或者其子類的異常\(catch_type為指向一個CONSTANT_Class_info型常量的索引),則轉(zhuǎn)到第handler_pc行繼續(xù)處理(對指定異常進行捕捉)。當catch_type的值為0時(對Exception進行捕捉),代表任意異常情況都需要轉(zhuǎn)向到handler_pc處進行處理。
異常表面上是Java代碼的一部分,編譯器使用異常表而不是簡單的跳轉(zhuǎn)命令來實現(xiàn)Java異常及finally處理機制

2.Exceptions屬性
這里的Exceptions屬性是在方法表中與Code屬性平級的一項屬性,與上面講的異常表不同(簡單理解就是上面的是操作,這個異常屬性是一個屬性值).Exception屬性的作用是列舉出方法中拋出的受查異常(Check Exception),也就是方法描述時throws關(guān)鍵字列舉出了異常.他的結(jié)構(gòu)如下

Exception屬性中的number_of_exception項表示方法可能拋出number_of_excptions種異常,每種受查異常使用一個exception_index_table項表示,exception_index_table是一個指向常量池中CONSTANT_Class_info型常量的索引,代表了該受查異常的類型。
3.LineNumberTable屬性
LineNumberTable屬性用于描述Java源碼行號與字節(jié)碼行號(字節(jié)碼的偏移量)之間的對應(yīng)關(guān)系.并不是運行時必須的屬性,但是默認會生成到Class文件之中,可以再Javac中分別使用-g:none或-g:lines選項來取消或要求生成這項信息。如果選擇不生成LineNumberTable屬性,對程序運行產(chǎn)生的最主要的影響就是當拋出異常時,堆棧中將不會顯示出錯的行號,并且在調(diào)試程序的時候,也無法按照源碼行來設(shè)置斷點
line_number_table是一個數(shù)量為line_number_table_length、類型為line_number_info的集合,line_number_info表包括了start_pc和line_number兩個u2類型的數(shù)據(jù)項,前者是字節(jié)碼行號,后者是Java源碼行號。
4.LocalVariableTable屬性
LocalVariableTable屬性用于描述棧幀中局部變量表中的變量于Java源碼中定義的變量之間的關(guān)系,它也不是運行時必須的屬性,但是默認會生成到Class文件之中.可以在Javac中分別使用-g:none或-g:vars選項來取消或要求生成這項信息。如果沒有生成這項屬性,最大的影響就是當其他人引用這個方法時,所有的參數(shù)名稱都將會丟失,IDE將會使用諸如arg0、arg1之類的占位符代替原有的參數(shù)名,這對程序運行沒有影響,但是會對代碼編寫帶來較大不便,而且在調(diào)試期間無法根據(jù)參數(shù)名稱從上下文中獲得參數(shù)值。

其中,local_variable_info項目代表了一個棧幀與源碼中的局部變量的關(guān)聯(lián)

start_pc和length屬性分別代表了這個局部變量的生命周期開始的字節(jié)碼偏移量及其作用范圍覆蓋的長度,兩者結(jié)合起來就是這個局部變量在字節(jié)碼之中的作用域范圍。
name_index和descriptor_index都是指向常量池中CONSTANT_Utf8_info型常量的索引,分別代表了局部變量的名稱以及這個局部變量的描述符。
index是這個局部變量在棧幀局部變量表中Slot的位置。當這個變量數(shù)據(jù)類型是64位類型時(double和long),它占用的Slot為index和index+1兩個。
5.SourceFile屬性
SourceFile屬性用于記錄生成這個Class文件的源碼文件名稱.這個屬性也是可選的,可以分別使用Javac的-g:none或-g:source選項來關(guān)閉或要求生成這項信息。在Java中,對于大多數(shù)的類來說,類名和文件名是一致的,但是有一些特殊情況(如內(nèi)部類)例外。如果不生成這項屬性,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名。

sourcefile_index數(shù)據(jù)項是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼文件的文件名
6.ConstantValue屬性
ConstantValue屬性的作用是通知虛擬機自動為靜態(tài)變量賦值。只有被static關(guān)鍵字修飾的變量(類變量)才可以使用這項屬性。類似“int x=123”和“static int x=123”這樣的變量定義在Java程序中是非常常見的事情,但虛擬機對這兩種變量賦值的方式和時刻都有所不同。對于非static類型的變量(也就是實例變量)的賦值是在實例構(gòu)造器<init>方法中進行的;而對于類變量,則有兩種方式可以選擇:在類構(gòu)造器<clinit>方法中或者使用ConstantValue屬性。目前Sun Javac編譯器的選擇是:如果同時使用final和static來修飾一個變量(按照習慣,這里稱“常量”更貼切),并且這個變量的數(shù)據(jù)類型是基本類型或者java.lang.String的話,就生成ConstantValue屬性來進行初始化,如果這個變量沒有被final修飾,或者并非基本類型及字符串,則將會選擇在<clinit>方法中進行初始化。
雖然有final關(guān)鍵字才更符合“ConstantValue”的語義,但虛擬機規(guī)范中并沒有強制要求字段必須設(shè)置了ACC_FINAL標志,只要求了有ConstantValue屬性的字段必須設(shè)置ACC_STATIC標志而已,對final關(guān)鍵字的要求是Javac編譯器自己加入的限制。而對ConstantValue的屬性值只能限于基本類型和String,因為此屬性的屬性值只是一個常量池的索引號,由于Class文件格式的常量類型中只有與基本屬性和字符串相對應(yīng)的字面量,所以就算ConstantValue屬性想支持別的類型也無能為力。

從數(shù)據(jù)結(jié)構(gòu)中可以看出,ConstantValue屬性是一個定長屬性,它的attribute_length數(shù)據(jù)項值必須固定為2。constantvalue_index數(shù)據(jù)項代表了常量池中一個字面量常量的引用,根據(jù)字段類型的不同,字面量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info、CONSTANT_String_info常量中的一種。
7.InnerClasses屬性
InnerClasses屬性用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)。如果一個類中定義了內(nèi)部類,那編譯器將會為它以及它所包含的內(nèi)部類生成InnerClasses屬性。

數(shù)據(jù)項number_of_classes代表需要記錄多少個內(nèi)部類信息,每一個內(nèi)部類的信息都由一個inner_classes_info表進行描述

inner_class_info_index和outer_class_info_index都是指向常量池中CONSTANT_Class_info型常量的索引,分別代表了內(nèi)部類和宿主類的符號引用。
inner_name_index是指向常量池中CONSTANT_Utf8_info型常量的索引,代表這個內(nèi)部類的名稱,如果是匿名內(nèi)部類,那么這項值為0。
inner_class_access_flags是內(nèi)部類的訪問標志,類似于類的access_flags,它的取值范圍如下表

8.Deprecated及Synthetic屬性
Deprecated和Synthetic兩個屬性都屬于標志類型的布爾屬性,只存在有和沒有的區(qū)別,沒有屬性值的概念
- Deprecated屬性用于表示某個類、字段或者方法,已經(jīng)被程序作者定為不再推薦使用,它可以通過在代碼中使用@deprecated注釋進行設(shè)置。
- Synthetic屬性代表此字段或者方法并不是由Java源碼直接產(chǎn)生的,而是由編譯器自行添加的,在JDK 1.5之后,標識一個類、字段或者方法是編譯器自動產(chǎn)生的,也可以設(shè)置它們訪問標志中的ACC_SYNTHETIC標志位,其中最典型的例子就是Bridge Method。所有由非用戶代碼產(chǎn)生的類、方法及字段都應(yīng)當至少設(shè)置Synthetic屬性和ACC_SYNTHETIC標志位中的一項,唯一的例外是實例構(gòu)造器“<init>”方法和類構(gòu)造器“<clinit>”方法
Deprecated和Synthetic屬性的結(jié)構(gòu)如下圖

其中attribute_length數(shù)據(jù)項的值必須為0x00000000,因為沒有任何屬性值需要設(shè)置 .
9.StackMapTable屬性
StackMapTable屬性在JDK 1.6發(fā)布后增加到了Class文件規(guī)范中,它是一個復雜的變長屬性,位于Code屬性的屬性表中。這個屬性會在虛擬機類加載的字節(jié)碼驗證階段被新類型檢查驗證器(Type Checker)使用(見7.3.2節(jié)),目的在于代替以前比較消耗性能的基于數(shù)據(jù)流分析的類型推導驗證器
StackMapTable屬性中包含零至多個棧映射幀(Stack Map Frames),每個棧映射幀都顯式或隱式地代表了一個字節(jié)碼偏移量,用于表示該執(zhí)行到該字節(jié)碼時局部變量表和操作數(shù)棧的驗證類型。類型檢查驗證器會通過檢查目標方法的局部變量和操作數(shù)棧所需要的類型來確定一段字節(jié)碼指令是否符合邏輯約束。

《Java虛擬機規(guī)范(Java SE 7版)》明確規(guī)定:在版本號大于或等于50.0的Class文件中,如果方法的Code屬性中沒有附帶StackMapTable屬性,那就意味著它帶有一個隱式的StackMap屬性。這個StackMap屬性的作用等同于number_of_entries值為0的StackMapTable屬性。一個方法的Code屬性最多只能有一個StackMapTable屬性,否則將拋出ClassFormatError異常。
10.Signature屬性
任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量(TypeVariables)或參數(shù)化類型(Parameterized Types),則Signature屬性會為它記錄泛型簽名信息.之所以要專門使用這樣一個屬性去記錄泛型類型,是因為Java語言的泛型采用的是擦除法實現(xiàn)的偽泛型.在字節(jié)碼(Code屬性)中,泛型信息編譯(類型變量、參數(shù)化類型)之后都通通被擦除掉。使用擦除法的好處是實現(xiàn)簡單(主要修改Javac編譯器,虛擬機內(nèi)部只做了很少的改動)、非常容易實現(xiàn)Backport,運行期也能夠節(jié)省一些類型所占的內(nèi)存空間。但壞處是運行期就無法像C#等有真泛型支持的語言那樣,將泛型類型與用戶定義的普通類型同等對待,例如運行期做反射時無法獲得到泛型信息。Signature屬性就是為了彌補這個缺陷而增設(shè)的,現(xiàn)在Java的反射API能夠獲取泛型類型,最終的數(shù)據(jù)來源也就是這個屬性.

其中signature_index項的值必須是一個對常量池的有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結(jié)構(gòu),表示類簽名、方法類型簽名或字段類型簽名。如果當前的Signature屬性是類文件的屬性,則這個結(jié)構(gòu)表示類簽名,如果當前的Signature屬性是方法表的屬性,則這個結(jié)構(gòu)表示方法類型簽名,如果當前Signature屬性是字段表的屬性,則這個結(jié)構(gòu)表示字段類型簽名。
11.BootstrapMethods屬性
用于保存invokedynamic指令引用的引導方法限定符。《Java虛擬機規(guī)范(Java SE 7版)》規(guī)定,如果某個類文件結(jié)構(gòu)的常量池中曾經(jīng)出現(xiàn)過CONSTANT_InvokeDynamic_info類型的常量,那么這個類文件的屬性表中必須存在一個明確的BootstrapMethods屬性,另外,即使CONSTANT_InvokeDynamic_info類型的常量在常量池中出現(xiàn)過多次,類文件的屬性表中最多也只能有一個BootstrapMethods屬性BootstrapMethods屬性與JSR-292中的InvokeDynamic指令和java.lang.Invoke包關(guān)系非常密切
目前的Javac暫時無法生成InvokeDynamic指令和BootstrapMethods屬性,必須通過一些非常規(guī)的手段才能使用到它們。


BootstrapMethods屬性中,num_bootstrap_methods項的值給出了bootstrap_methods[]數(shù)組中的引導方法限定符的數(shù)量。而bootstrap_methods[]數(shù)組的每個成員包含了一個指向常量池CONSTANT_MethodHandle結(jié)構(gòu)的索引值,它代表了一個引導方法,還包含了這個引導方法靜態(tài)參數(shù)的序列(可能為空)。bootstrap_methods[]數(shù)組中的每個成員必須包含以下3項內(nèi)容。
- bootstrap_method_ref:bootstrap_method_ref項的值必須是一個對常量池的有效索引。常量池在該索引處的值必須是一個CONSTANT_MethodHandle_info結(jié)構(gòu)。
- num_bootstrap_arguments:num_bootstrap_arguments項的值給出了bootstrap_arguments[]數(shù)組成員的數(shù)量。
- bootstrap_arguments[]:bootstrap_arguments[]數(shù)組的每個成員必須是一個對常量池的有效
索引。常量池在該索引處必須是下列結(jié)構(gòu)之一:
CONSTANT_String_info、CONSTANT_Class_info、CONSTANT_Integer_info、CONSTANT_Long_info、
CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_MethodHandle_info或CONSTANT_MethodType_info。
`
