平臺無關:Sun公司以及其餘的虛擬機提供商發佈了許多能夠運行在各類不一樣平臺上的虛擬機,這些虛擬機均可以載入和執行同一種平臺無關的字節碼,從而實現了程序的「一次編寫,處處運行」。
語言無關:語言無關的基礎是虛擬機和字節碼存儲格式,Java虛擬機不和任何語言(包括Java)綁定,它只與Class文件這種特定的二進制文件格式所關聯,Class文件中包含了Java虛擬機指令集和符號表以及若干其餘輔助信息。java
Class文件是一組以8位字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內容幾乎所有是程序運行的必要數據,沒有空隙存在。當遇到須要佔用8位字節以上空間的數據項時,則會按照高位在前的方式分割成若干個8位字節進行存儲。bootstrap
魔數:每一個Class文件的頭4個字節稱爲魔數(Magic Number),其惟一做用是肯定這個文件是否爲一個能被虛擬機接受的Class文件。值爲0xCAFEBABE。
Class的版本號:緊接着魔數的4個字節存儲的是Class的版本號——第5個和第6個字節是次版本號(Minor Version),第7個和第8個字節是主版本號(Major Version)。
版本號兼容:高版本的JDK只能向下兼容之前版本的Class文件,不能運行之後版本的Class文件。數組
常量池:緊接着主次版本號後的是常量池,也能夠理解爲Class文件的資源倉庫,它是與其餘項目關聯最多的數據類型,也是佔用Class文件空間最大的數據項目之一,同時還算第一個出現的表類型數據項目。
常量池計數值:因爲常量池中常量數量不固定,所以在入口處要放置一項u2類型的數據,表明常量池計數值(從1開始,由於計數的0表明「不引用任何一個常量池項目」的含義)。
常量池存放數據:常量池中主要存放兩大類常量——字面量(Literal)和符號引用(Symbolic References)。字面量比較接近於Java語言層面的常量概念——如文本字符串、聲明爲final的常量值等。符號引用則屬於編譯原理方面的概念,包括下面三類常量:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符。
動態鏈接:Java代碼在javac編譯的時候,並無鏈接這一步驟,而是在虛擬機加載Class文件的時候動態鏈接。
常量池中的項:常量池中每一項都是一個表,截止到JDK 7中更用14種各不相同的表結構數據,其共同特色就是表開始的第一位是一個u1類型的標識位。
安全
在常量池結束以後,緊接着的兩個字節表明訪問標誌(access_flags),這個標誌用於識別一些類或者接口層次的訪問信息,包括:這個Class是類仍是接口;是否認義爲public類型;是否認義爲abstract類型;若是是類的話,是否被聲明爲final等。
數據結構
類索引和父類索引:是一個u2類型的數據,用於肯定這個類的全限定類名和父類的全限定類名,指向一個類型爲CONSTANT_Class_info的類描述符常量,經過CONSTANT_Class_info類型的常量中的索引類型能夠找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。
接口索引集合:是一組u2類型的數據集合,用於描述這個類實現了哪些接口,這些被實現的接口按照從左到右排列在接口索引集合中。入口的第一項——u2類型的數據爲接口計數器,表示索引表的容量;若是沒有實現任何接口,則該計數器爲0。架構
字段表:字段表(field_info)用於描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量,但不包括在方法內部聲明的局部變量。
一個字段包括的信息有:字段的做用域(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 |
標誌名稱 | 標誌值 | 含義 |
---|---|---|
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 |
name_index是對常量池的引用,表明着字段的簡單名稱。簡單名稱是指沒有類型和參數修飾的方法或者字段名稱,這個類中的inc()方法和m字段的簡單名稱分別是「inc」和「m」。
全限定名:如下面代碼爲例,「org/xxx/clazz/TestClass」是這個類的全限定名,僅僅是把類全名中的「.」替換成了「/」而已,爲了使連續的多個全限定名之間不產生混淆,在使用時最後通常會加入一個「;」表示全限定名結束。ide
public class TestClass { private int m; public int inc() { return m + 1; } }
descriptor_index也是對常量池的引用,表明着字段和方法的描述符。描述符的做用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。根據描述符規則,基本數據類型(byte、char、double、float、int、long、short、boolean)以及表明無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示。性能
標識字符 | 含義 | 標識字符 | 含義 |
---|---|---|---|
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;」,,一個整型數組「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」。優化
方法表的結構如同字段表同樣,依次包括了訪問標誌(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表結合(attributes)幾項,如字段表所示。
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 方法是否爲public |
ACC_PRIVATE | 0x0002 | 方法是否爲private |
ACC_PROTECTED | 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 | 方法是否由編譯器自動產生的 |
方法裏的Java代碼,通過編譯器編譯成字節碼指令後,存放在方法屬性集合中一個名爲「Code」的屬性裏面,屬性表做爲Class文件格式中最具擴展性的一種數據項目。
與字段表集合相對應的,若是父類方法在子類彙總沒有被重寫(Override),方法表集合中就不會出現來自父類的方法信息。
有可能會出現由編譯器自動添加的方法,最典型的即是類構造器「<clinit>」方法和實例構造器「<init>」方法。
在Java語言中,要重載(Overload)一個方法,除了要與原方法具備相同的簡單名稱以外,還要求必須擁有一個與原方法不一樣的特徵簽名,特徵簽名就是一個方法中各個參數在常量池中的字段符號引用的集合,也就是由於返回值不會包含在特徵簽名中,所以Java語言裏面是沒法僅僅依靠返回值的不一樣來對一個已有方法進行重載的。可是在Class文件格式彙總,特徵簽名的範圍更大一些,只要描述符不是徹底一致的兩個方法也能夠共存。也就是說,若是兩個方法有相同的名稱和特徵簽名,但返回值不一樣,那麼也是能夠合法共存於同一個Class文件中的。
在Class文件、字段表、方法表中均可以攜帶本身的屬性表集合,以用於描述某些場景專有的信息。與Class文件中其餘的數據項目要求嚴格的順序、長度和內容不一樣,屬性表集合的限制稍微寬鬆了一些,再也不要求各個屬性表具備嚴格順序,而且只要不與已有屬性名重複,任何人實現的編譯器均可以向屬性表寫入本身定義的屬性信息,Java虛擬機運行時會忽略掉他不認識的屬性。
屬性名稱須要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性的結構則是徹底自定義的,只須要經過一個u4的長度屬性去說明屬性值所佔用的位數便可。一個符合規則的屬性表應該知足下表所定義的結構:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節碼指令 |
ConstantValue | 字段表 | final關鍵字定義的常量值 |
Deprecated | 類、方法表、字段表 | 被聲明爲deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
EnclosingMethod | 類文件 | 僅當一個類爲局部類或者匿名類時才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法 |
InnerClasses | 類文件 | 內部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節碼指令的對用關係 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
StackMapTable | Code屬性 | JDK1.6中新增的屬性,供新的類型檢查驗證器(Type Checker)檢查和處理目標方法的局部變量和操做數棧所須要的類型是否匹配 |
Signature | 類、方法表、字段表 | JDK1.5中新增的屬性,這個屬性用於支持泛型狀況下的方法簽名,在Java語言中,任何類、接口、初始化方法或成員的泛型簽名若是包含了類型變量(Type Variables)或參數化類型(Parameterized Types),則Signature屬性會爲他記錄泛型簽名信息。因爲Java的泛型採用擦除法實現,在爲了不類型信息被擦出後致使簽名混亂,須要這個屬性記錄泛型中的相關信息 |
SourceFile | 類文件 | 記錄源文件名稱 |
SourceDebugExtension | 類文件 | JDK 1.6中新增的屬性,SourceDebugExtension屬性用於存儲額外的調試信息,譬如在進行JSP文件調試時,沒法同構Java堆棧來定位到JSP文件的行號,JSR-45規範爲這些非Java語言編寫,卻須要編譯成字節碼並運行在Java虛擬機中的程序提供了一個進行調試的標準機制,使用SourceDebugExtension屬性就能夠用於存儲這個標準所新加入的調試信息 |
Synthetic | 類、方法表、字段表 | 標識方法或字段爲編譯器自動生成的 |
LocalVariableTypeTable | 類 | JDK 1.5中新增的屬性,他使用特徵簽名代替描述符,是爲了引入泛型語法以後能描述泛型參數化類型而添加 |
RuntimeVisibleAnnotations | 類、方法表、字段表 | JDK 1.5中新增的屬性,爲動態註解提供支持。RuntimeVisibleAnnotations屬性用於指明哪些註解是運行時(實際上運行時就是進行反射調用)可見的 |
RuntimeInVisibleAnnotations | 類、方法表、字段表 | JDK 1.5新增的屬性,與RuntimeVisibleAnnotations屬性做用恰好相反,用於指明哪些註解是運行時不可見的 |
RuntimeVisibleParameter Annotations | 方法表 | JDK 1.5新增的屬性,做用與RuntimeVisibleAnnotations屬性相似,只不過做用對象爲方法參數 |
RuntimeInVisibleAnnotations Annotations | 方法表 | JDK 1.5中新增的屬性,做用與RuntimeInVisibleAnnotations屬性相似,只不過做用對象爲方法參數 |
AnnotationDefault | 方法表 | JDK 1.5中新增的屬性,用於記錄註解類元素的默認值 |
BootstrapMethods | 類文件 | JDK 1.7中新增的屬性,用於保存invokedynamic指令引用的引導方法限定符 |
Code屬性是Class文件中最重要的一個屬性,若是把一個Java程序中的信息分爲代碼(Code,方法體裏面的Java代碼)和元數據(Metadata,包括類、字段、方法定義及其餘信息)兩部分,那麼在整個Class文件中,Code屬性用於描述代碼,全部的其餘數據項目都用於描述元數據。
Java程序方法體中的代碼通過Javac編譯器處理後,最終變爲字節碼指令存儲在Code屬性內。Code屬性出如今方法表的屬性集合之中,但並不是全部的方法表都必須存在這個屬性,譬如接口或者抽象類中的方法就不存在Code屬性。若是方法表有Code屬性存在,那麼他的結構將以下表所示。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | max_stack | 1 |
u2 | max_locals | 1 |
u4 | code_length | 1 |
u1 | code | code_length |
u2 | exception_table_length | 1 |
exception_info | exception_table | exception_table_length |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
這裏的Exceptions屬性是在方法表與Code屬性平級的一項屬性。Exceptions屬性的做用是列舉出方法中可能拋出的受查異常(Checked Exceptions),也就是說方法描述時在throws關鍵字啊後面列舉的異常。他的結構見下表。
類型 | 名稱 | 數量 | 類型 | 名稱 | 數量 |
---|---|---|---|---|---|
u2 | attribute_name_index | 1 | u2 | number_of_exceptions | 1 |
u4 | attribute_length | 1 | u2 | exception_index_table | number_of_exceptions |
LineNumberTable屬性用於描述Java源碼行號與字節碼行號(字節碼的偏移量)之間的對應關係。他並非運行時必須的屬性,但默認生成到Class文件之中,能夠在Javac中分別使用-g : none或-g : lines選項來取消或要求生成這項信息。若是選擇不生成LineNumberTable屬性,對程序運行產生的最主要的影響就是當拋出異常時,堆棧中將不會顯示出錯的行號,而且在調試程序的時候,也沒法按照源碼行來設置斷點。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 |
LocalVariableTable屬性用於描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關係,她也不是運行時必須的屬性,但默認會生成到Class文件之中,能夠在Javac中分別使用-g : none或-g :vars選項來取消或要求生成這項信息。若是沒有生成這項屬性,最大的影響就是當前其餘人引用這個方法時,全部的參數名稱都將會丟失,IDE將會使用諸如arg0、arg1之類的佔位符代替原有的參數名,這對程序運行沒有影響,可是會對代碼編寫帶來較大不便,並且在調試期間沒法根據參數名稱從上下文中得到參數值。LocalVariableTable屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | local_variable_table_length | 1 |
local_variable_info | local_variable_table | local_variable_table_length |
u2 | start_pc | 1 |
u2 | length | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | index | 1 |
SourceFile屬性用於記錄生成這個Class文件的源碼文件名稱。這個屬性也是可選的,能夠分別使用Javac的-g:none
或-g: source
選項來關閉或要求生成這項信息。在Java中,對於大多數的類來講,類名和文件名是一致的,可是有一些特殊狀況(如內部類)例外。若是不生成這項屬性,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名。這個屬性是一個定長的屬性,其結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | sourcefile_index |
ConstantValue屬性的做用是通知虛擬機自動爲靜態變量賦值。只有被static關鍵字修飾的變量(類變量)纔可使用這項屬性。
相似「int x = 123」和「static int x=123」這樣的變量定義在Java程序中是很是常見的事情,但虛擬機對這兩種變量賦值的方法和時刻都有所不一樣。對於非static類型的變量(也就是實例變量)的賦值是在實例構造器<init>方法中進行的;而對於類變量,則有兩種方式能夠選擇:在類構造器<clinit>方法中或者使用ConstantValue屬性。目前Sun Javac編譯器的選擇是:若是同時使用final和static來修飾一個變量(按照習慣,這裏稱「常量」更貼切),而且這個變量的數據類型是基本類型或者java.lang.String的話,就生成ConstantValue屬性來進行初始化,若是這個變量沒有被final修飾,或者並不是基本類型及字符串,則將會選擇在<clinit>方法中進行初始化。
雖然有final關鍵字才更符合「ConstantValue」的語義,但虛擬機規範中並無強制要求字段必須設置了ACC_FINAL標誌,只要求了有ConstantValue屬性的字段必須設置ACC_STATIC標誌而已,對final關鍵字的要求是javac編譯器本身加入的限制。而對ConstantValue屬性值只能限於基本類型和String,不過不認爲這是什麼限制,由於此屬性的屬性值只是一個常量池的索引號,因爲Class文件格式的常量類型中只有與基本屬性和字符串相對應的字面量,因此就算ConstantValue屬性在想支持別的類型也無能爲力。ConstantValue屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | constantvalue_index | 1 |
InnerClasses屬性用於記錄內部類與宿主類之間的關聯。若是一個類中定義了內部類,那編譯器將會爲他以及他所包含的內部類生成InnerClasses屬性。該屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_class | 1 |
inner_classes_info | inner_class | number_of_classes |
類型 | 名稱 | 數量 |
---|---|---|
u2 | inner_class_info_index | 1 |
u2 | outer_class_info_index | 1 |
u2 | inner_name_index | 1 |
u2 | inner_class_access_info | 1 |
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 內部類是否爲public |
ACC_PRIVATE | 0x0002 | 內部類是否爲private |
ACC_PROTECTED | 0x0004 | 內部類是否爲protected |
ACC_STATIC | 0x0008 | 內部類是否爲static |
ACC_FINAL | 0x0010 | 內部類是否爲final |
ACC_INTERFACE | 0x0020 | 內部類是否爲synchronized |
ACC_ABSTRACT | 0x0400 | 內部類是否爲abstract |
ACC_SYNTHETIC | 0x1000 | 內部類是否嬪妃由用戶代碼產生的 |
ACC_ANNOTATION | 0x2000 | 內部類是不是一個註解 |
ACC_ENUM | 0x4000 | 內部類是不是一個枚舉 |
Deprecated和Synthetic兩個屬性都屬於標誌類型的布爾屬性,只存在有和沒有的區別,沒有屬性值的概念。屬性的結構很是簡單,其中attribute_length數據項的值必須爲0x00000000,由於沒有任何屬性值須要設置,見下表:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
StackMapTable屬性在JDK 1.6發佈周增長到了Class文件規範中,他是一個複雜的變長屬性,位於Code屬性的屬性表,這個屬性會在虛擬機類加載的字節碼驗證階段被新類型檢查驗證器(Type Checker)使用,目的在於代替之前比較消耗性能的基於數據流分析的類型推導驗證器。
這個類型檢查驗證器最初來源於Sheng Liang爲Java ME CLDC實現的字節碼驗證器。新的驗證器在一樣能保證Class文件合法性的前提下,省略了在運行期經過數據流分析確認字節碼的行爲邏輯合法性的步驟,而是在編譯階段將一系列的驗證類型(Verification Types)直接記錄在Class文件之中,經過檢查這些驗證類型代替了類型推導過程,從而大幅提高了字節碼驗證的性能。這個驗證器在JDK 1.6中首次提供,並在JDK 1.7中強制代替本來基於類型推斷的字節碼驗證器。
StackMapTable屬性中包含零至多個棧映射棧(Stack Map Frames),每一個棧映射幀都顯示或隱式的表明了一個字節碼偏移量,用於表示該執行到該字節碼時局部變量表和操做數棧的驗證類型。類型檢查驗證器會經過檢查目標方法的局部變量和操做數棧所須要的類型來肯定一段字節碼指令是否符合邏輯約束。StackMapTable屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_entries | 1 |
stack_map_frame | stack_map_frame_entries | number_of_entries |
《Java虛擬機規範(Java SE 7版)》明確規定:在版本號大於或等於50.0的Class文件中,若是方法的Code屬性中沒有附帶StackMapTable屬性,那就意味着他帶有一個隱式的StackMap屬性。這個StackMap屬性的做用等同於number_of_entries值爲0的StackMapTable屬性。一個方法的Code屬性最多隻能有一個StackMapTable屬性,不然將拋出ClassFormatError異常。
Signature屬性在JDK 1.5發佈後增長到了Class文件規範之中,他是一個可選的定長屬性,能夠出現於類、屬性表和方法表結構的屬性表中。在JDK 1.5大幅加強了Java語言的語法,在此以後,任何類、接口、初始化方法或成員的泛型簽名若是包含餓了類型變量(Type Variables)或參數化類型(Parameterized Types),則Signature屬性會爲他記錄泛型簽名信息。之因此要專門使用這樣一個屬性去記錄泛型類型,是由於Java語言的泛型採用的是擦除法實現的僞泛型,在字節碼(Code屬性)中,泛型信息編譯(類型變量、參數化類型)以後都通通被擦除掉。使用擦除法的好處是實現簡單(主要修改Javac編譯器,虛擬機內部只作了不多的改動)、很是容易實現Backport,運行期也可以節省一些類型所佔的內存空間。但壞處是運行期就沒法像C#等有真泛型支持的語言那樣,將泛型類型與用戶定義的普通類型同等對待,例如運行期作反射時沒法得到到泛型信息。Signature屬性就是爲了彌補這個缺陷而增設的,如今Java的反射API可以獲取泛型類型,最終的數據來源也就是這個屬性。Signature屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | signature_index | 1 |
其中signature_index項的值必須是一個對常量池的有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示類簽名、方法類型簽名或字段類型簽名。若是當前的Signature屬性是類文件的屬性,則這個結構表示類簽名,若是當前的Signature屬性是方法表的屬性,則這個結構表示方法類型簽名,若是當前Signature屬性是字段表的屬性,則這個結構表示字段類型簽名。
BootstrapMethods屬性在JDK 1.7發佈後增長到了Class文件規範之中,他是一個複雜的變長屬性,位於類文件的屬性表中。這個屬性用於保存invokedynamic指令引用的引導方法限定符。《Java虛擬機規範(Java SE 7版)》規定,若是某個類文件結構的常量池中曾經出現過CONSTANT_InvokeDynamic_info類型的常量,那麼這個類文件的屬性表中必須存在一個明確地BootstrapMethods屬性,另外,即便CONSTANT_InvokeDynamic_info類型的常量在常量池中出現過屢次,類文件的屬性表中最多也只能一個BootstrapMethods屬性。BootstrapMethods屬性與JSR-292中的InvokeDynamic指令和java.lang.Invoke包關係很是密切。
目前的Javac暫時沒法生成InvokeDynamic指令和BootstrapMethods屬性,必須經過一些很是規的手段才能使用到他們,也許在不久的未來,等JSR-292更加成熟一些,這種情況就會改變。BootstrapMethods屬性的結構見下表。
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | num_bootstrap_methods | 1 |
bootstrap_method | bootstrap_methods | num_bootstrap_methods |
類型 | 名稱 | 數量 |
---|---|---|
u2 | bootstrap_method_ref | 1 |
u2 | num_bootstrap_arguments | 1 |
u2 | bootstrap_arguments | num_bootstrap_arguments |
Java虛擬機的指令由一個字節長度的、表明着某種特定操做含義的數字(稱爲操做碼,Opcode)以及跟隨其後的零至多個表明此操做所需參數(稱爲操做數,Operands)而構成。因爲Java虛擬機採用面向操做數棧而不是寄存器的架構,因此大多數的指令都不包含操做數,只有一個操做碼。
操做碼總數:Java虛擬機操做碼的長度爲一個字節,這意味着指令集的操做碼總數不可能超過256條
放棄操做數對齊:因爲Class文件格式放棄了編譯後代碼的操做數長度對齊,這就意味着虛擬機處理那些超過一個字節數據的時候,不得不在運行時從字節中重建出具體數據的結構,若是要將一個16位長度的無符號整數使用兩個無符號字節存儲起來(將它們命名爲byte1和byte2),那他們的值應該是這樣的:
(byte1 << 8) | byte2
加載和存儲指令用於將數據在棧幀中的局部變量表和操做數棧之間來回傳輸,這類指令包括以下內容。
iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>
istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_、lconst_<l>、fconst_<f>、dconst_<d>
wide
運算或算術指令用於對兩個操做數棧上的值進行某種特定運算,並把結果從新存入到操做棧頂。大致上算術指令能夠分爲兩種:對整型數據進行運算的指令與對浮點型數據進行運算的指令。因爲沒有直接支持byte、short、char和boolean類型的算術指令,對於這類數據的運算,應使用操做int類型的指令代替。整數與浮點數的算術指令在溢出和被零除的時候也有各自不一樣的行爲表現,全部的算術指令以下:
類型轉換指令能夠將兩種不一樣的數值類型進行相互轉換,JVM直接支持小範圍類型向大範圍類型的安全轉換,而處理大範圍類型到小範圍類型的窄化類型轉換則須要顯示地使用轉換指令來完成,這些指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。
窄化類型轉換會致使結果產生不一樣的正負號、不一樣的數量級、數值精度丟失的狀況,但永遠不可能拋出運行時異常。
類實例與數組都屬於對象,可是其建立與操做使用了不一樣的字節碼指令,指令以下:
如同操做一個普通數據結構中的堆棧那樣,Java虛擬機提供了一些用於直接操做數棧的指令,包括:
控制轉移指令可讓Java虛擬機有條件或無條件的從指定的位置指令而不是控制轉移指令的下一條指令繼續執行程序,從概念模型上理解,能夠認爲控制轉移指令就是在有條件或無條件的修改PC寄存器的值。控制轉移指令以下。
int、reference、null指令集:在Java虛擬機中有專門的指令集用來處理int和reference類型的條件分支比較操做;爲了能夠無需明顯標識一個實體值是否null,也有專門的指令用來檢測null值。
轉化成int類型:與算術運算時的規則一致,對於boolean類型、byte類型、char類型和short類型的條件分支比較操做,則會先執行相應類型的比較運算指令(dcmpg、dcmpl、fcmpg、fcmpl、lcmp),運算指令會返回一個整形值到操做數棧中,隨後再執行int類型的條件分支比較操做來完成整個分支跳轉。因爲各類類型的比較最終都會轉化爲int類型的比較操做,int類型比較是否方便完善就顯得尤其重要,因此Java虛擬機提供的int類型的條件分支指令是最爲豐富和強大的。
方法調用指令與數據類型無關,而方法返回指令是根據返回值的類型區分的,包括ireturn(當返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn和areturn;另外還有一條return指令供聲明爲void的方法、實例初始化方法以及類和接口的類初始化方法使用。如下列舉了5條用於方法調用的指令:
Java虛擬機能夠支持方法級的同步和方法內部一段指令序列的同步,這兩種同步結構是使用管程(Monitor)來支持的。
管程:管程能夠看作一個軟件模塊,它是將共享的變量和對於這些共享變量的操做封裝起來,造成一個具備必定接口的功能模塊,進程能夠調用管程來實現進程級別的併發控制。進程只能互斥得使用管程,即當一個進程使用管程時,另外一個進程必須等待。當一個進程使用完管程後,它必須釋放管程並喚醒等待管程的某一個進程。在管程入口處的等待隊列稱爲入口等待隊列,因爲進程會執行喚醒操做,所以可能有多個等待使用管程的隊列,這樣的隊列稱爲緊急隊列,它的優先級高於等待隊列。
信號量:信號量是一種抽象數據類型,由一個整形 (sem)變量和兩個原子操做組成:
- P():sem減1,若是sem<0等待,不然繼續;
- V():sem加1,若是sem<=0,說明當前有等着的進程,喚醒掛在信號量上的等待進程,能夠是一個或多個 。
Java虛擬機規範描繪了Java虛擬機應有的共同程序存儲格式:Class文件格式以及字節碼指令集。這些內容與硬件、操做系統及具體的Java虛擬機實現之間是徹底獨立的。
Java虛擬機實現必須可以讀取Class文件並精確實現包含在其中的Java虛擬機代碼的語義,一個優秀的虛擬機實現,在知足虛擬機規範的約束下對具體實現作出修改和優化也是徹底可行的,而且虛擬機規範中明確鼓勵實現者這樣作。只要優化後Class文件依然能夠被正確讀取,而且包含在其中的語義能獲得完整的保持,那實現者就能夠選擇任何方式去實現這些語義。
虛擬機實現者可使用這種伸縮性來讓Java虛擬機得到更高的性能、更低的內存消耗或者更好的可移植性,選擇哪一種特性取決於Java虛擬機實現的目標和關注點是什麼。虛擬機實現的方式主要有如下兩種: