摘要:類文件的結(jié)構(gòu)文件是一組以位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊地排列在文件之中,中間沒(méi)有添加任何分隔符,這使得整個(gè)文件中存儲(chǔ)的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù),沒(méi)有空隙存在。
點(diǎn)擊進(jìn)入我的博客 4.1 字節(jié)碼
平臺(tái)無(wú)關(guān):Sun公司以及其他的虛擬機(jī)提供商發(fā)布了許多可以運(yùn)行在各種不同平臺(tái)上的虛擬機(jī),這些虛擬機(jī)都可以載入和執(zhí)行同一種平臺(tái)無(wú)關(guān)的字節(jié)碼,從而實(shí)現(xiàn)了程序的“一次編寫,到處運(yùn)行”。
語(yǔ)言無(wú)關(guān):語(yǔ)言無(wú)關(guān)的基礎(chǔ)是虛擬機(jī)和字節(jié)碼存儲(chǔ)格式,Java虛擬機(jī)不和任何語(yǔ)言(包括Java)綁定,它只與Class文件這種特定的二進(jìn)制文件格式所關(guān)聯(lián),Class文件中包含了Java虛擬機(jī)指令集和符號(hào)表以及若干其他輔助信息。
Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊地排列在Class文件之中,中間沒(méi)有添加任何分隔符,這使得整個(gè)Class文件中存儲(chǔ)的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù),沒(méi)有空隙存在。當(dāng)遇到需要占用8位字節(jié)以上空間的數(shù)據(jù)項(xiàng)時(shí),則會(huì)按照高位在前的方式分割成若干個(gè)8位字節(jié)進(jìn)行存儲(chǔ)。
Class文件只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)、表。
無(wú)符號(hào)數(shù):無(wú)符號(hào)數(shù)屬于基本的數(shù)據(jù)類型,以u(píng)1、u2、u4、u8來(lái)分別代表1個(gè)字節(jié)、2個(gè)字節(jié)、4個(gè)字節(jié)和8個(gè)字節(jié)的無(wú)符號(hào)數(shù)。無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。
表:表是由多個(gè)無(wú)符號(hào)數(shù)或其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型,表習(xí)慣性以_info結(jié)尾。表用于描述有層次的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個(gè)Class文件本質(zhì)上就是一張表,由以下的數(shù)據(jù)項(xiàng)構(gòu)成。
容量計(jì)數(shù)器:無(wú)論是無(wú)符號(hào)數(shù)還是表,當(dāng)需要描述同一類型但數(shù)量不定的多個(gè)數(shù)據(jù)時(shí),經(jīng)常會(huì)使用一個(gè)前置的容量計(jì)數(shù)器加若干連續(xù)的數(shù)據(jù)項(xiàng)的形式。
4.2.1 魔數(shù)與Class文件的版本魔數(shù):每個(gè)Class文件的頭4個(gè)字節(jié)稱為魔數(shù)(Magic Number),其唯一作用是確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件。值為0xCAFEBABE。
Class的版本號(hào):緊接著魔數(shù)的4個(gè)字節(jié)存儲(chǔ)的是Class的版本號(hào)——第5個(gè)和第6個(gè)字節(jié)是次版本號(hào)(Minor Version),第7個(gè)和第8個(gè)字節(jié)是主版本號(hào)(Major Version)。
版本號(hào)兼容:高版本的JDK只能向下兼容以前版本的Class文件,不能運(yùn)行以后版本的Class文件。
常量池:緊接著主次版本號(hào)后的是常量池,也可以理解為Class文件的資源倉(cāng)庫(kù),它是與其他項(xiàng)目關(guān)聯(lián)最多的數(shù)據(jù)類型,也是占用Class文件空間最大的數(shù)據(jù)項(xiàng)目之一,同時(shí)還算第一個(gè)出現(xiàn)的表類型數(shù)據(jù)項(xiàng)目。
常量池計(jì)數(shù)值:由于常量池中常量數(shù)量不固定,因此在入口處要放置一項(xiàng)u2類型的數(shù)據(jù),代表常量池計(jì)數(shù)值(從1開(kāi)始,因?yàn)橛?jì)數(shù)的0代表“不引用任何一個(gè)常量池項(xiàng)目”的含義)。
常量池存放數(shù)據(jù):常量池中主要存放兩大類常量——字面量(Literal)和符號(hào)引用(Symbolic References)。字面量比較接近于Java語(yǔ)言層面的常量概念——如文本字符串、聲明為final的常量值等。符號(hào)引用則屬于編譯原理方面的概念,包括下面三類常量:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符。
動(dòng)態(tài)連接:Java代碼在javac編譯的時(shí)候,并沒(méi)有連接這一步驟,而是在虛擬機(jī)加載Class文件的時(shí)候動(dòng)態(tài)連接。
常量池中的項(xiàng):常量池中每一項(xiàng)都是一個(gè)表,截止到JDK 7中更用14種各不相同的表結(jié)構(gòu)數(shù)據(jù),其共同特點(diǎn)就是表開(kāi)始的第一位是一個(gè)u1類型的標(biāo)識(shí)位。
在常量池結(jié)束之后,緊接著的兩個(gè)字節(jié)代表訪問(wèn)標(biāo)志(access_flags),這個(gè)標(biāo)志用于識(shí)別一些類或者接口層次的訪問(wèn)信息,包括:這個(gè)Class是類還是接口;是否定義為public類型;是否定義為abstract類型;如果是類的話,是否被聲明為final等。
類索引和父類索引:是一個(gè)u2類型的數(shù)據(jù),用于確定這個(gè)類的全限定類名和父類的全限定類名,指向一個(gè)類型為CONSTANT_Class_info的類描述符常量,通過(guò)CONSTANT_Class_info類型的常量中的索引類型可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。
接口索引集合:是一組u2類型的數(shù)據(jù)集合,用于描述這個(gè)類實(shí)現(xiàn)了哪些接口,這些被實(shí)現(xiàn)的接口按照從左到右排列在接口索引集合中。入口的第一項(xiàng)——u2類型的數(shù)據(jù)為接口計(jì)數(shù)器,表示索引表的容量;如果沒(méi)有實(shí)現(xiàn)任何接口,則該計(jì)數(shù)器為0。
字段表:字段表(field_info)用于描述接口或者類中聲明的變量。字段(field)包括類級(jí)變量以及實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的局部變量。
一個(gè)字段包括的信息有:字段的作用域(public、private、protected修飾符)、是實(shí)例變量還是類變量(static修飾符)、可變性(final)、并發(fā)可見(jiàn)性(volatile修飾符,是否強(qiáng)制從主內(nèi)存讀寫)、可否被被序列化(transient修飾符)、字段數(shù)據(jù)類型(基本類型、對(duì)象、數(shù)組)、字段名稱。
修飾符布爾值:上述這些信息中,各個(gè)修飾符都是布爾值,要么有某個(gè)修飾符,要么沒(méi)有,很適合使用標(biāo)志位來(lái)表示。而字段叫什么名字、字段被定義為什么數(shù)據(jù)類型,這些都是無(wú)法固定的,只能引用常量池中的常量來(lái)描述。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
標(biāo)志名稱 | 標(biāo)志值 | 含義 |
---|---|---|
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 | 字段是否由編譯器自動(dòng)產(chǎn)生的 |
ACC_ENUM | 0x4000 | 字段是否enum |
name_index是對(duì)常量池的引用,代表著字段的簡(jiǎn)單名稱。簡(jiǎn)單名稱是指沒(méi)有類型和參數(shù)修飾的方法或者字段名稱,這個(gè)類中的inc()方法和m字段的簡(jiǎn)單名稱分別是“inc”和“m”。
全限定名:以下面代碼為例,“org/xxx/clazz/TestClass”是這個(gè)類的全限定名,僅僅是把類全名中的“.”替換成了“/”而已,為了使連續(xù)的多個(gè)全限定名之間不產(chǎn)生混淆,在使用時(shí)最后一般會(huì)加入一個(gè)“;”表示全限定名結(jié)束。
public class TestClass { private int m; public int inc() { return m + 1; } }
descriptor_index也是對(duì)常量池的引用,代表著字段和方法的描述符。描述符的作用是用來(lái)描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型(byte、char、double、float、int、long、short、boolean)以及代表無(wú)返回值的void類型都用一個(gè)大寫字符來(lái)表示,而對(duì)象類型則用字符L加對(duì)象的全限定名來(lái)表示。
標(biāo)識(shí)字符 | 含義 | 標(biāo)識(shí)字符 | 含義 |
---|---|---|---|
B | 基本類型byte | J | 基本類型long |
C | 基本類型char | S | 基本類型short |
D | 基本類型double | Z | 基本類型boolean |
F | 基本類型float | V | 特殊類型void |
I | 基本類型int | L | 對(duì)象類型,如Ljava/lang/Object |
數(shù)組類型:每一維度將使用一個(gè)前置的“[”字符來(lái)描述,如一個(gè)定義為“java.lang.String[][]”類型的二維數(shù)組,將被記錄為:“[[Ljava/lang/String;”,,一個(gè)整型數(shù)組“int[]”被記錄為“[I”。
描述方法:按照先參數(shù)列表,后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組小括號(hào)“( )”之內(nè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”。
字段表都包含的固定數(shù)據(jù)項(xiàng)目到descriptor_index為止就結(jié)束了,不過(guò)在descriptor_index之后跟隨著一個(gè)屬性表集合用于存儲(chǔ)一些額外的信息,字段都可以在屬性表中描述零至多項(xiàng)的額外信息。對(duì)于本例中的字段m,他的屬性表計(jì)數(shù)器為0,也就是說(shuō)沒(méi)有需要額外描述的信息,但是,如果將字段m的聲明改為“final static int m=123”,那就可能會(huì)存在一項(xiàng)名稱為ConstantValue的屬性,其值指向常量123。
字段表集合中不會(huì)列出從超類或者父接口中繼承而來(lái)的字段,但有可能列出原本Java代碼之中不存在的字段,譬如在內(nèi)部類中為了保持對(duì)外部類的訪問(wèn)性,會(huì)自動(dòng)添加指向外部類實(shí)例的字段。
在Java語(yǔ)言中字段是無(wú)法重載的,兩個(gè)字段的數(shù)據(jù)類型、修飾符不管是否相同,都必須使用不一樣的名稱,但是對(duì)于字節(jié)碼來(lái)講,如果兩個(gè)字段的描述符不一致,那字段重名就是合法的。
4.2.6 方法表集合方法表的結(jié)構(gòu)如同字段表一樣,依次包括了訪問(wèn)標(biāo)志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表結(jié)合(attributes)幾項(xiàng),如字段表所示。
標(biāo)志名稱 | 標(biāo)志值 | 含義 |
---|---|---|
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 | 方法是否由編譯器產(chǎn)生的橋接方法 |
ACC_VARARGS | 0x0080 | 方法是否接受不定參數(shù) |
ACC_NATIVE | 0x0100 | 方法是否為native |
ACC_ABSTRACT | 0x0400 | 方法是否為abstract |
ACC_STRICTFP | 0x0800 | 方法是否為strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否由編譯器自動(dòng)產(chǎn)生的 |
方法里的Java代碼,經(jīng)過(guò)編譯器編譯成字節(jié)碼指令后,存放在方法屬性集合中一個(gè)名為“Code”的屬性里面,屬性表作為Class文件格式中最具擴(kuò)展性的一種數(shù)據(jù)項(xiàng)目。
與字段表集合相對(duì)應(yīng)的,如果父類方法在子類匯總沒(méi)有被重寫(Override),方法表集合中就不會(huì)出現(xiàn)來(lái)自父類的方法信息。
有可能會(huì)出現(xiàn)由編譯器自動(dòng)添加的方法,最典型的便是類構(gòu)造器“
在Java語(yǔ)言中,要重載(Overload)一個(gè)方法,除了要與原方法具有相同的簡(jiǎn)單名稱之外,還要求必須擁有一個(gè)與原方法不同的特征簽名,特征簽名就是一個(gè)方法中各個(gè)參數(shù)在常量池中的字段符號(hào)引用的集合,也就是因?yàn)榉祷刂挡粫?huì)包含在特征簽名中,因此Java語(yǔ)言里面是無(wú)法僅僅依靠返回值的不同來(lái)對(duì)一個(gè)已有方法進(jìn)行重載的。但是在Class文件格式匯總,特征簽名的范圍更大一些,只要描述符不是完全一致的兩個(gè)方法也可以共存。也就是說(shuō),如果兩個(gè)方法有相同的名稱和特征簽名,但返回值不同,那么也是可以合法共存于同一個(gè)Class文件中的。
4.2.7 屬性表集合在Class文件、字段表、方法表中都可以攜帶自己的屬性表集合,以用于描述某些場(chǎng)景專有的信息。與Class文件中其他的數(shù)據(jù)項(xiàng)目要求嚴(yán)格的順序、長(zhǎng)度和內(nèi)容不同,屬性表集合的限制稍微寬松了一些,不再要求各個(gè)屬性表具有嚴(yán)格順序,并且只要不與已有屬性名重復(fù),任何人實(shí)現(xiàn)的編譯器都可以向?qū)傩员韺懭胱约憾x的屬性信息,Java虛擬機(jī)運(yùn)行時(shí)會(huì)忽略掉他不認(rèn)識(shí)的屬性。
屬性名稱需要從常量池中引用一個(gè)CONSTANT_Utf8_info類型的常量來(lái)表示,而屬性的結(jié)構(gòu)則是完全自定義的,只需要通過(guò)一個(gè)u4的長(zhǎng)度屬性去說(shuō)明屬性值所占用的位數(shù)即可。一個(gè)符合規(guī)則的屬性表應(yīng)該滿足下表所定義的結(jié)構(gòu):
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節(jié)碼指令 |
ConstantValue | 字段表 | final關(guān)鍵字定義的常量值 |
Deprecated | 類、方法表、字段表 | 被聲明為deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
EnclosingMethod | 類文件 | 僅當(dāng)一個(gè)類為局部類或者匿名類時(shí)才能擁有這個(gè)屬性,這個(gè)屬性用于標(biāo)識(shí)這個(gè)類所在的外圍方法 |
InnerClasses | 類文件 | 內(nèi)部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號(hào)與字節(jié)碼指令的對(duì)用關(guān)系 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
StackMapTable | Code屬性 | JDK1.6中新增的屬性,供新的類型檢查驗(yàn)證器(Type Checker)檢查和處理目標(biāo)方法的局部變量和操作數(shù)棧所需要的類型是否匹配 |
Signature | 類、方法表、字段表 | JDK1.5中新增的屬性,這個(gè)屬性用于支持泛型情況下的方法簽名,在Java語(yǔ)言中,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數(shù)化類型(Parameterized Types),則Signature屬性會(huì)為他記錄泛型簽名信息。由于Java的泛型采用擦除法實(shí)現(xiàn),在為了避免類型信息被擦出后導(dǎo)致簽名混亂,需要這個(gè)屬性記錄泛型中的相關(guān)信息 |
SourceFile | 類文件 | 記錄源文件名稱 |
SourceDebugExtension | 類文件 | JDK 1.6中新增的屬性,SourceDebugExtension屬性用于存儲(chǔ)額外的調(diào)試信息,譬如在進(jìn)行JSP文件調(diào)試時(shí),無(wú)法同構(gòu)Java堆棧來(lái)定位到JSP文件的行號(hào),JSR-45規(guī)范為這些非Java語(yǔ)言編寫,卻需要編譯成字節(jié)碼并運(yùn)行在Java虛擬機(jī)中的程序提供了一個(gè)進(jìn)行調(diào)試的標(biāo)準(zhǔn)機(jī)制,使用SourceDebugExtension屬性就可以用于存儲(chǔ)這個(gè)標(biāo)準(zhǔn)所新加入的調(diào)試信息 |
Synthetic | 類、方法表、字段表 | 標(biāo)識(shí)方法或字段為編譯器自動(dòng)生成的 |
LocalVariableTypeTable | 類 | JDK 1.5中新增的屬性,他使用特征簽名代替描述符,是為了引入泛型語(yǔ)法之后能描述泛型參數(shù)化類型而添加 |
RuntimeVisibleAnnotations | 類、方法表、字段表 | JDK 1.5中新增的屬性,為動(dòng)態(tài)注解提供支持。RuntimeVisibleAnnotations屬性用于指明哪些注解是運(yùn)行時(shí)(實(shí)際上運(yùn)行時(shí)就是進(jìn)行反射調(diào)用)可見(jiàn)的 |
RuntimeInVisibleAnnotations | 類、方法表、字段表 | JDK 1.5新增的屬性,與RuntimeVisibleAnnotations屬性作用剛好相反,用于指明哪些注解是運(yùn)行時(shí)不可見(jiàn)的 |
RuntimeVisibleParameter Annotations | 方法表 | JDK 1.5新增的屬性,作用與RuntimeVisibleAnnotations屬性類似,只不過(guò)作用對(duì)象為方法參數(shù) |
RuntimeInVisibleAnnotations Annotations | 方法表 | JDK 1.5中新增的屬性,作用與RuntimeInVisibleAnnotations屬性類似,只不過(guò)作用對(duì)象為方法參數(shù) |
AnnotationDefault | 方法表 | JDK 1.5中新增的屬性,用于記錄注解類元素的默認(rèn)值 |
BootstrapMethods | 類文件 | JDK 1.7中新增的屬性,用于保存invokedynamic指令引用的引導(dǎo)方法限定符 |
Code屬性是Class文件中最重要的一個(gè)屬性,如果把一個(gè)Java程序中的信息分為代碼(Code,方法體里面的Java代碼)和元數(shù)據(jù)(Metadata,包括類、字段、方法定義及其他信息)兩部分,那么在整個(gè)Class文件中,Code屬性用于描述代碼,所有的其他數(shù)據(jù)項(xiàng)目都用于描述元數(shù)據(jù)。
Java程序方法體中的代碼經(jīng)過(guò)Javac編譯器處理后,最終變?yōu)樽止?jié)碼指令存儲(chǔ)在Code屬性內(nèi)。Code屬性出現(xiàn)在方法表的屬性集合之中,但并非所有的方法表都必須存在這個(gè)屬性,譬如接口或者抽象類中的方法就不存在Code屬性。如果方法表有Code屬性存在,那么他的結(jié)構(gòu)將如下表所示。
類型 | 名稱 | 數(shù)量 |
---|---|---|
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 |
attribute_name_index:是一項(xiàng)指向CONSTANT_Utf8_info型常量的索引,常量值固定為“Code”,他代表了該屬性的屬性名稱。
attribute_length:指示了屬性值的長(zhǎng)度,由于屬性名稱索引與屬性長(zhǎng)度一共為6個(gè)字節(jié),所以屬性值的長(zhǎng)度固定為整個(gè)屬性表長(zhǎng)度減少6個(gè)字節(jié)。
max_stack:代表了操作數(shù)棧(Operand Stacks)深度的最大值。在方法執(zhí)行的任意時(shí)刻,操作數(shù)棧都不會(huì)超過(guò)這個(gè)深度。虛擬機(jī)運(yùn)行的時(shí)候需要根據(jù)這個(gè)值分配棧幀(Stack Frame)中的操作幀深度。
max_locals:代表了局部變量表所需的存儲(chǔ)空間。在這里,max_locals的單位是Slot,Slot是虛擬機(jī)為局部變量分配內(nèi)存所使用的最小單位。對(duì)于byte、char、float、int、short、boolean和returnAddress等長(zhǎng)度不超過(guò)32位的數(shù)據(jù)類型,每個(gè)局部變量占用1個(gè)Slot,而double和long這兩種64位的數(shù)據(jù)類型則需要兩個(gè)Slot來(lái)存放。方法參數(shù)(包括實(shí)例方法中的隱藏參數(shù)“this”)、顯式異常處理器的參數(shù)(Exception Handler Parameter,就是try-catch語(yǔ)句中catch塊所定義的異常)、方法體中定義的局部變量都需要使用局部變量表來(lái)存放。另外,并不是在方法中用到了多少個(gè)局部變量,就把這些局部變量所占Slot之和作為max_locals的值,原因是局部變量表中的Slot可以重用,當(dāng)代碼執(zhí)行超出一個(gè)局部變量的作用域時(shí),這個(gè)局部變量所占的Slot可以被其他局部變量所使用,Javac編譯器會(huì)根據(jù)變量的作用域來(lái)分配Slot給各個(gè)變量使用,然后計(jì)算出max_locals的大小。
code_length和code:用來(lái)存儲(chǔ)java源程序編譯后生成的字節(jié)碼指令。code_length代表字節(jié)碼長(zhǎng)度,code是用于存儲(chǔ)字節(jié)碼指令的一系列字節(jié)流。既然叫字節(jié)碼指令,那么每個(gè)指令就是一個(gè)u1類型的單字節(jié),當(dāng)虛擬機(jī)讀取到code中的一個(gè)字節(jié)碼時(shí),就可以對(duì)應(yīng)找出這個(gè)字節(jié)碼代表的是什么指令,并且可以知道這條指令后面是否需要跟隨參數(shù),以及參數(shù)應(yīng)當(dāng)如何理解。我們知道一個(gè)u1數(shù)據(jù)類型的取值范圍為0x00~0xFF,對(duì)應(yīng)十進(jìn)制的0~255,也就是一共可以表達(dá)256條指令,目前,Java虛擬機(jī)規(guī)范已經(jīng)定義了其中約200條編碼值對(duì)應(yīng)的指令含義。
關(guān)于code_length:有一件值得注意的事情,雖然他是一個(gè)u4類型的長(zhǎng)度值,理論上最大值可以達(dá)到2的32次方減1,但是虛擬機(jī)規(guī)范中明確限制了一個(gè)方法不允許超過(guò)65535條字節(jié)碼指令,即他實(shí)際只使用了u2的長(zhǎng)度,如果超過(guò)這個(gè)限制,Javac編譯器也會(huì)拒絕編譯。一般來(lái)講,編寫Java代碼時(shí)只要不是刻意去編寫一個(gè)超長(zhǎng)的方法來(lái)為難編譯器,是不太可能超過(guò)這個(gè)最大值的限制。但是,某些特殊情況,例如在編譯一個(gè)很復(fù)雜的JSP文件時(shí),某些JSP編譯器會(huì)把JSP內(nèi)容和頁(yè)面輸出的信息歸并于一個(gè)方法之中,就可能因?yàn)榉椒ㄉ勺止?jié)碼超長(zhǎng)的原因而導(dǎo)致編譯失敗。
這里的Exceptions屬性是在方法表與Code屬性平級(jí)的一項(xiàng)屬性。Exceptions屬性的作用是列舉出方法中可能拋出的受查異常(Checked Exceptions),也就是說(shuō)方法描述時(shí)在throws關(guān)鍵字啊后面列舉的異常。他的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 | 類型 | 名稱 | 數(shù)量 |
---|---|---|---|---|---|
u2 | attribute_name_index | 1 | u2 | number_of_exceptions | 1 |
u4 | attribute_length | 1 | u2 | exception_index_table | number_of_exceptions |
number_of_exceptions:項(xiàng)表示方法可能拋出number_of_exceptions種受查異常
exception_index_table:每一種受查異常使用一個(gè)exception_index_table項(xiàng)表示,exception_index_table是一個(gè)指向常量池中CONSTANT_Class_info型常量的索引,代表了該受查異常的類型。
LineNumberTable屬性用于描述Java源碼行號(hào)與字節(jié)碼行號(hào)(字節(jié)碼的偏移量)之間的對(duì)應(yīng)關(guān)系。他并不是運(yùn)行時(shí)必須的屬性,但默認(rèn)生成到Class文件之中,可以在Javac中分別使用-g : none或-g : lines選項(xiàng)來(lái)取消或要求生成這項(xiàng)信息。如果選擇不生成LineNumberTable屬性,對(duì)程序運(yùn)行產(chǎn)生的最主要的影響就是當(dāng)拋出異常時(shí),堆棧中將不會(huì)顯示出錯(cuò)的行號(hào),并且在調(diào)試程序的時(shí)候,也無(wú)法按照源碼行來(lái)設(shè)置斷點(diǎn)。LineNumberTable屬性的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
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:是一個(gè)數(shù)量為line_number_table_length、類型為line_number_info的集合
line_number_info表:包括了start_pc和line_number兩個(gè)u2類型的數(shù)據(jù)項(xiàng),前者是字節(jié)碼行號(hào),后者是Java源碼行號(hào)。
LocalVariableTable屬性用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關(guān)系,她也不是運(yùn)行時(shí)必須的屬性,但默認(rèn)會(huì)生成到Class文件之中,可以在Javac中分別使用-g : none或-g :vars選項(xiàng)來(lái)取消或要求生成這項(xiàng)信息。如果沒(méi)有生成這項(xiàng)屬性,最大的影響就是當(dāng)前其他人引用這個(gè)方法時(shí),所有的參數(shù)名稱都將會(huì)丟失,IDE將會(huì)使用諸如arg0、arg1之類的占位符代替原有的參數(shù)名,這對(duì)程序運(yùn)行沒(méi)有影響,但是會(huì)對(duì)代碼編寫帶來(lái)較大不便,而且在調(diào)試期間無(wú)法根據(jù)參數(shù)名稱從上下文中獲得參數(shù)值。LocalVariableTable屬性的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
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 |
start_pc和length:屬性分別代表了這個(gè)局部變量的生命周期開(kāi)始地字節(jié)碼偏移量及其作用范圍覆蓋的長(zhǎng)度,兩者結(jié)合起來(lái)就是這個(gè)局部變量在字節(jié)碼之中的作用域范圍。
name_index和descriptor_index:都是指向常量池中CONSTANT_Utf8_info型常量的索引,分別代表了局部變量的名稱以及這個(gè)局部變量的描述符。
index:是這個(gè)局部變量在棧幀局部變量表中Slot的位置。當(dāng)這個(gè)變量數(shù)據(jù)類型是64位類型時(shí)(double和long),他占用的Slot為index和index+1兩個(gè)。
姐妹屬性:在JDK1.5引入泛型之后,LocalVariableTable屬性增加了一個(gè)“姐妹屬性”:LocalVariableTypeTable,這個(gè)新增的屬性結(jié)構(gòu)與LocalVariableTable非常相似,僅僅是吧記錄的字段描述符的descriptor_index替換成了字段的特征簽名(Signature),對(duì)于非泛型類型來(lái)說(shuō),描述符和特征簽名能描述的信息是基本一致的,但是泛型引入后,由于描述符中反省的參數(shù)化類型被擦除掉,描述符就不能準(zhǔn)確的描述泛型類型了,因此出現(xiàn)了LocalVariableTypeTable。
SourceFile屬性用于記錄生成這個(gè)Class文件的源碼文件名稱。這個(gè)屬性也是可選的,可以分別使用Javac的-g:none或-g: source選項(xiàng)來(lái)關(guān)閉或要求生成這項(xiàng)信息。在Java中,對(duì)于大多數(shù)的類來(lái)說(shuō),類名和文件名是一致的,但是有一些特殊情況(如內(nèi)部類)例外。如果不生成這項(xiàng)屬性,當(dāng)拋出異常時(shí),堆棧中將不會(huì)顯示出錯(cuò)代碼所屬的文件名。這個(gè)屬性是一個(gè)定長(zhǎng)的屬性,其結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | sourcefile_index | ? |
sourcefile_index數(shù)據(jù)項(xiàng):是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼我呢見(jiàn)的文件名。
ConstantValue屬性的作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值。只有被static關(guān)鍵字修飾的變量(類變量)才可以使用這項(xiàng)屬性。
類似“int x = 123”和“static int x=123”這樣的變量定義在Java程序中是非常常見(jiàn)的事情,但虛擬機(jī)對(duì)這兩種變量賦值的方法和時(shí)刻都有所不同。對(duì)于非static類型的變量(也就是實(shí)例變量)的賦值是在實(shí)例構(gòu)造器
雖然有final關(guān)鍵字才更符合“ConstantValue”的語(yǔ)義,但虛擬機(jī)規(guī)范中并沒(méi)有強(qiáng)制要求字段必須設(shè)置了ACC_FINAL標(biāo)志,只要求了有ConstantValue屬性的字段必須設(shè)置ACC_STATIC標(biāo)志而已,對(duì)final關(guān)鍵字的要求是javac編譯器自己加入的限制。而對(duì)ConstantValue屬性值只能限于基本類型和String,不過(guò)不認(rèn)為這是什么限制,因?yàn)榇藢傩缘膶傩灾抵皇且粋€(gè)常量池的索引號(hào),由于Class文件格式的常量類型中只有與基本屬性和字符串相對(duì)應(yīng)的字面量,所以就算ConstantValue屬性在想支持別的類型也無(wú)能為力。ConstantValue屬性的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | constantvalue_index | 1 |
ConstantValue屬性:是一個(gè)定長(zhǎng)屬性,他的attribute_length數(shù)據(jù)項(xiàng)值必須固定為2。
constantvalue_index數(shù)據(jù)項(xiàng):代表了常量池中一個(gè)字面量常量的引用,根據(jù)字段類型的不同,字面量可以是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info、CONSTANT_String_info常量中的一種。
InnerClasses屬性用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)。如果一個(gè)類中定義了內(nèi)部類,那編譯器將會(huì)為他以及他所包含的內(nèi)部類生成InnerClasses屬性。該屬性的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | number_of_class | 1 |
inner_classes_info | inner_class | number_of_classes |
number_of_classes:代表需要記錄多少個(gè)內(nèi)部類信息。
inner_classes_info表:每一個(gè)內(nèi)部類的信息都由一個(gè)inner_classes_info表進(jìn)行描述。inner_classes_info的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | inner_class_info_index | 1 |
u2 | outer_class_info_index | 1 |
u2 | inner_name_index | 1 |
u2 | inner_class_access_info | 1 |
inner_name_index:是指向常量池中CONSTANT_Utf8_info型常量的索引,代表這個(gè)內(nèi)部類的名稱,如果是匿名內(nèi)部類,那么這項(xiàng)值為0.
inner_class_access_flags:是內(nèi)部類的訪問(wèn)標(biāo)志,類似于類的access_flags,他的取值范圍見(jiàn)下表。
標(biāo)志名稱 | 標(biāo)志值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 內(nèi)部類是否為public |
ACC_PRIVATE | 0x0002 | 內(nèi)部類是否為private |
ACC_PROTECTED | 0x0004 | 內(nèi)部類是否為protected |
ACC_STATIC | 0x0008 | 內(nèi)部類是否為static |
ACC_FINAL | 0x0010 | 內(nèi)部類是否為final |
ACC_INTERFACE | 0x0020 | 內(nèi)部類是否為synchronized |
ACC_ABSTRACT | 0x0400 | 內(nèi)部類是否為abstract |
ACC_SYNTHETIC | 0x1000 | 內(nèi)部類是否嬪妃由用戶代碼產(chǎn)生的 |
ACC_ANNOTATION | 0x2000 | 內(nèi)部類是否是一個(gè)注解 |
ACC_ENUM | 0x4000 | 內(nèi)部類是否是一個(gè)枚舉 |
Deprecated和Synthetic兩個(gè)屬性都屬于標(biāo)志類型的布爾屬性,只存在有和沒(méi)有的區(qū)別,沒(méi)有屬性值的概念。屬性的結(jié)構(gòu)非常簡(jiǎn)單,其中attribute_length數(shù)據(jù)項(xiàng)的值必須為0x00000000,因?yàn)闆](méi)有任何屬性值需要設(shè)置,見(jiàn)下表:
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
Deprecated屬性用于表示每個(gè)類、字段或者方法,已經(jīng)被程序作者定位不在推薦使用,他可以通過(guò)在代碼中使用@deprecated注釋進(jìn)行設(shè)置。
Synthetic屬性代表此字段或者方法并不是由Java源碼直接產(chǎn)生的,而是由編譯器自行添加的,在JDK 1.5之后,標(biāo)識(shí)一個(gè)類、字段或者方法是編譯器自動(dòng)產(chǎn)生的,也可以設(shè)置他們?cè)L問(wèn)標(biāo)志中的ACC_SYNTHETIC標(biāo)志位,其中最典型的例子就是Bridge Method。所有由非用戶代碼產(chǎn)生的類、方法及字段都應(yīng)當(dāng)至少設(shè)置Synthetic屬性和ACC_SYNTHETIC標(biāo)志位中的一項(xiàng),唯一的例外是實(shí)例構(gòu)造器“
???????
StackMapTable屬性在JDK 1.6發(fā)布周增加到了Class文件規(guī)范中,他是一個(gè)復(fù)雜的變長(zhǎng)屬性,位于Code屬性的屬性表,這個(gè)屬性會(huì)在虛擬機(jī)類加載的字節(jié)碼驗(yàn)證階段被新類型檢查驗(yàn)證器(Type Checker)使用,目的在于代替以前比較消耗性能的基于數(shù)據(jù)流分析的類型推導(dǎo)驗(yàn)證器。
這個(gè)類型檢查驗(yàn)證器最初來(lái)源于Sheng Liang為Java ME CLDC實(shí)現(xiàn)的字節(jié)碼驗(yàn)證器。新的驗(yàn)證器在同樣能保證Class文件合法性的前提下,省略了在運(yùn)行期通過(guò)數(shù)據(jù)流分析確認(rèn)字節(jié)碼的行為邏輯合法性的步驟,而是在編譯階段將一系列的驗(yàn)證類型(Verification Types)直接記錄在Class文件之中,通過(guò)檢查這些驗(yàn)證類型代替了類型推導(dǎo)過(guò)程,從而大幅提升了字節(jié)碼驗(yàn)證的性能。這個(gè)驗(yàn)證器在JDK 1.6中首次提供,并在JDK 1.7中強(qiáng)制代替原本基于類型推斷的字節(jié)碼驗(yàn)證器。
StackMapTable屬性中包含零至多個(gè)棧映射棧(Stack Map Frames),每個(gè)棧映射幀都顯示或隱式的代表了一個(gè)字節(jié)碼偏移量,用于表示該執(zhí)行到該字節(jié)碼時(shí)局部變量表和操作數(shù)棧的驗(yàn)證類型。類型檢查驗(yàn)證器會(huì)通過(guò)檢查目標(biāo)方法的局部變量和操作數(shù)棧所需要的類型來(lái)確定一段字節(jié)碼指令是否符合邏輯約束。StackMapTable屬性的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
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虛擬機(jī)規(guī)范(Java SE 7版)》明確規(guī)定:在版本號(hào)大于或等于50.0的Class文件中,如果方法的Code屬性中沒(méi)有附帶StackMapTable屬性,那就意味著他帶有一個(gè)隱式的StackMap屬性。這個(gè)StackMap屬性的作用等同于number_of_entries值為0的StackMapTable屬性。一個(gè)方法的Code屬性最多只能有一個(gè)StackMapTable屬性,否則將拋出ClassFormatError異常。
Signature屬性在JDK 1.5發(fā)布后增加到了Class文件規(guī)范之中,他是一個(gè)可選的定長(zhǎng)屬性,可以出現(xiàn)于類、屬性表和方法表結(jié)構(gòu)的屬性表中。在JDK 1.5大幅增強(qiáng)了Java語(yǔ)言的語(yǔ)法,在此之后,任何類、接口、初始化方法或成員的泛型簽名如果包含餓了類型變量(Type Variables)或參數(shù)化類型(Parameterized Types),則Signature屬性會(huì)為他記錄泛型簽名信息。之所以要專門使用這樣一個(gè)屬性去記錄泛型類型,是因?yàn)镴ava語(yǔ)言的泛型采用的是擦除法實(shí)現(xiàn)的偽泛型,在字節(jié)碼(Code屬性)中,泛型信息編譯(類型變量、參數(shù)化類型)之后都統(tǒng)統(tǒng)被擦除掉。使用擦除法的好處是實(shí)現(xiàn)簡(jiǎn)單(主要修改Javac編譯器,虛擬機(jī)內(nèi)部只做了很少的改動(dòng))、非常容易實(shí)現(xiàn)Backport,運(yùn)行期也能夠節(jié)省一些類型所占的內(nèi)存空間。但壞處是運(yùn)行期就無(wú)法像C#等有真泛型支持的語(yǔ)言那樣,將泛型類型與用戶定義的普通類型同等對(duì)待,例如運(yùn)行期做反射時(shí)無(wú)法獲得到泛型信息。Signature屬性就是為了彌補(bǔ)這個(gè)缺陷而增設(shè)的,現(xiàn)在Java的反射API能夠獲取泛型類型,最終的數(shù)據(jù)來(lái)源也就是這個(gè)屬性。Signature屬性的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | signature_index | 1 |
其中signature_index項(xiàng)的值必須是一個(gè)對(duì)常量池的有效索引。常量池在該索引處的項(xiàng)必須是CONSTANT_Utf8_info結(jié)構(gòu),表示類簽名、方法類型簽名或字段類型簽名。如果當(dāng)前的Signature屬性是類文件的屬性,則這個(gè)結(jié)構(gòu)表示類簽名,如果當(dāng)前的Signature屬性是方法表的屬性,則這個(gè)結(jié)構(gòu)表示方法類型簽名,如果當(dāng)前Signature屬性是字段表的屬性,則這個(gè)結(jié)構(gòu)表示字段類型簽名。
BootstrapMethods屬性在JDK 1.7發(fā)布后增加到了Class文件規(guī)范之中,他是一個(gè)復(fù)雜的變長(zhǎng)屬性,位于類文件的屬性表中。這個(gè)屬性用于保存invokedynamic指令引用的引導(dǎo)方法限定符?!禞ava虛擬機(jī)規(guī)范(Java SE 7版)》規(guī)定,如果某個(gè)類文件結(jié)構(gòu)的常量池中曾經(jīng)出現(xiàn)過(guò)CONSTANT_InvokeDynamic_info類型的常量,那么這個(gè)類文件的屬性表中必須存在一個(gè)明確地BootstrapMethods屬性,另外,即使CONSTANT_InvokeDynamic_info類型的常量在常量池中出現(xiàn)過(guò)多次,類文件的屬性表中最多也只能一個(gè)BootstrapMethods屬性。BootstrapMethods屬性與JSR-292中的InvokeDynamic指令和java.lang.Invoke包關(guān)系非常密切。
目前的Javac暫時(shí)無(wú)法生成InvokeDynamic指令和BootstrapMethods屬性,必須通過(guò)一些非常規(guī)的手段才能使用到他們,也許在不久的將來(lái),等JSR-292更加成熟一些,這種狀況就會(huì)改變。BootstrapMethods屬性的結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | num_bootstrap_methods | 1 |
bootstrap_method | bootstrap_methods | num_bootstrap_methods |
num_bootstrap_methods:項(xiàng)的值給出了bootstrap_methods[]數(shù)組中的引導(dǎo)方法限定符的數(shù)量。
bootstrap_methods[]數(shù)組:的每個(gè)成員包含了一個(gè)指向常量池CONSTANT_MethodHandle結(jié)構(gòu)的索引值,他代表了一個(gè)引導(dǎo)方法,還包含了這個(gè)引導(dǎo)方法靜態(tài)參數(shù)的序列(可能為空)。
bootstrap_method:結(jié)構(gòu)見(jiàn)下表。
類型 | 名稱 | 數(shù)量 |
---|---|---|
u2 | bootstrap_method_ref | 1 |
u2 | num_bootstrap_arguments | 1 |
u2 | bootstrap_arguments | num_bootstrap_arguments |
bootstrap_method_ref:bootstrap_method_ref項(xiàng)的值必須是一個(gè)對(duì)常量池的有效索引。常量池在該索引處的值必須是一個(gè)CONSTANT_MethodHandle_info結(jié)構(gòu)。
num_bootstrap_arguments:num_bootstrap_arguments項(xiàng)的值給出了bootstrap_arguments[]數(shù)組成員的數(shù)量。
bootstrap_arguments[]:bootstrap_arguments[]數(shù)組的每個(gè)成員必須是一個(gè)對(duì)常量池的有效索引。常量池在該索引處必須是下列結(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。
4.3 字節(jié)碼指令Java虛擬機(jī)的指令由一個(gè)字節(jié)長(zhǎng)度的、代表著某種特定操作含義的數(shù)字(稱為操作碼,Opcode)以及跟隨其后的零至多個(gè)代表此操作所需參數(shù)(稱為操作數(shù),Operands)而構(gòu)成。由于Java虛擬機(jī)采用面向操作數(shù)棧而不是寄存器的架構(gòu),所以大多數(shù)的指令都不包含操作數(shù),只有一個(gè)操作碼。
操作碼總數(shù):Java虛擬機(jī)操作碼的長(zhǎng)度為一個(gè)字節(jié),這意味著指令集的操作碼總數(shù)不可能超過(guò)256條
放棄操作數(shù)對(duì)齊:由于Class文件格式放棄了編譯后代碼的操作數(shù)長(zhǎng)度對(duì)齊,這就意味著虛擬機(jī)處理那些超過(guò)一個(gè)字節(jié)數(shù)據(jù)的時(shí)候,不得不在運(yùn)行時(shí)從字節(jié)中重建出具體數(shù)據(jù)的結(jié)構(gòu),如果要將一個(gè)16位長(zhǎng)度的無(wú)符號(hào)整數(shù)使用兩個(gè)無(wú)符號(hào)字節(jié)存儲(chǔ)起來(lái)(將它們命名為byte1和byte2),那他們的值應(yīng)該是這樣的:
(byte1 << 8) | byte24.3.1 字節(jié)碼與數(shù)據(jù)類型
大多數(shù)的指令都包含了其操作所對(duì)應(yīng)的數(shù)據(jù)類型信息,iload指令用于從局部變量表中加載int型的數(shù)據(jù)到操作數(shù)棧中,而fload指令加載的則是float類型的數(shù)據(jù)。
大部分與數(shù)據(jù)類型相關(guān)的字節(jié)碼指令,他們的操作碼助記符中都有特殊的字符來(lái)表明專門為哪種數(shù)據(jù)類型服務(wù):i代表對(duì)int類型的數(shù)據(jù)操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。
有一些指令的助記符中沒(méi)有明確地指明操作類型的字母,如arraylength指令,他沒(méi)有代表數(shù)據(jù)類型的特殊字符,但操作數(shù)永遠(yuǎn)只能是一個(gè)數(shù)組類型的對(duì)象。
還有一些指令如無(wú)條件跳轉(zhuǎn)指令goto則是與數(shù)據(jù)類型無(wú)關(guān)的。
由于Java虛擬機(jī)的操作碼最多只有256個(gè),Java虛擬機(jī)的指令被設(shè)計(jì)成非完全獨(dú)立的(Java虛擬機(jī)規(guī)范中把這種特性稱為“Not Orthogonal”,即并非每種數(shù)據(jù)類型和每一種操作都有對(duì)應(yīng)的指令)。
大部分的指令都沒(méi)有支持整數(shù)類型byte、char和short,甚至沒(méi)有任何指令支持boolean類型。編譯器會(huì)在編譯器或運(yùn)行期將byte和short類型的數(shù)據(jù)帶符號(hào)擴(kuò)展(Sign-Extend)為相應(yīng)的int類型數(shù)據(jù),將boolean和char類型數(shù)據(jù)零位擴(kuò)展(Zero-Extend)為相應(yīng)的int類型數(shù)據(jù)。與之類似,在處理boolean、byte、short和char類型的數(shù)組時(shí),也會(huì)轉(zhuǎn)換為使用對(duì)應(yīng)的int類型的字節(jié)碼指令來(lái)處理。因此,大多數(shù)對(duì)于boolean、byte、short和char類型數(shù)據(jù)的操作,實(shí)際上都是使用相應(yīng)的int類型作為運(yùn)算類型(Computational Type)。
4.3.2 加載和存儲(chǔ)指令加載和存儲(chǔ)指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來(lái)回傳輸,這類指令包括如下內(nèi)容。
將一個(gè)局部變量加載到操作棧:
iload、iload_、lload、lload_ 、fload、fload_ 、dload、dload_ 、aload、aload_
將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:
istore、istore_、lstore、lstore_ 、fstore、fstore_ 、dstore、dstore_ 、astore、astore_
將一個(gè)常量加載到操作數(shù)棧:
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_、lconst_、fconst_ 、dconst_
擴(kuò)充局部變量表的訪問(wèn)索引的指令:
wide
以尖括號(hào)結(jié)尾的(例如iload_
運(yùn)算或算術(shù)指令用于對(duì)兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定運(yùn)算,并把結(jié)果重新存入到操作棧頂。大體上算術(shù)指令可以分為兩種:對(duì)整型數(shù)據(jù)進(jìn)行運(yùn)算的指令與對(duì)浮點(diǎn)型數(shù)據(jù)進(jìn)行運(yùn)算的指令。由于沒(méi)有直接支持byte、short、char和boolean類型的算術(shù)指令,對(duì)于這類數(shù)據(jù)的運(yùn)算,應(yīng)使用操作int類型的指令代替。整數(shù)與浮點(diǎn)數(shù)的算術(shù)指令在溢出和被零除的時(shí)候也有各自不同的行為表現(xiàn),所有的算術(shù)指令如下:
加法指令:iadd、ladd、fadd、dadd。
減法指令:isub、lsub、fsub、dsub。
乘法指令:imul、lmul、fmul、dmul。
除法指令:idiv、ldiv、fdiv、ddiv。
求余指令:irem、lrem、frem、drem。
取反指令:ineg、lneg、fneg、dneg。
位移指令:ishl、ishr、iushr、lshl、lshr、lushr。
按位或指令:ior、lor。
按位與指令:iand、land。
按位異或指令:ixor、lxor。
局部變量自增指令:iinc。
比較指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp。
在處理整型數(shù)據(jù)時(shí),只有除法指令(idiv和ldiv)以及求余指令(irem和lrem)中當(dāng)出現(xiàn)除數(shù)為零時(shí)會(huì)導(dǎo)致虛擬機(jī)拋出ArithmeticException異常,其余任何整型數(shù)運(yùn)算場(chǎng)景都不應(yīng)該拋出運(yùn)行時(shí)異常。
對(duì)long類型數(shù)值進(jìn)行比較時(shí),虛擬機(jī)采用帶符號(hào)的比較方式,而
虛擬機(jī)在處理浮點(diǎn)數(shù)時(shí)必須嚴(yán)格遵循IEEE 754規(guī)范中所規(guī)定的行為和限制。也就是說(shuō),Java虛擬機(jī)必須完全支持IEEE 754中定義的非正規(guī)浮點(diǎn)數(shù)值(Denormalized Floating-Point Numbers)和逐級(jí)下溢(Gradual Underflow)的運(yùn)算規(guī)則。
所有的運(yùn)算結(jié)果都必須舍入到適當(dāng)?shù)木?,非精確的結(jié)果必須舍入為可被表示的最接近的精確值,如果有兩種可表示的形式與該值一樣接近,將優(yōu)先選擇最低有效位為零的。
Java虛擬機(jī)在處理浮點(diǎn)數(shù)運(yùn)算時(shí),不會(huì)拋出任何運(yùn)行時(shí)異常(這里所講的是Java語(yǔ)言中的異常,勿與IEEE 754規(guī)范中的浮點(diǎn)異?;ハ嗷煜?,IEEE 754的浮點(diǎn)異常是一種運(yùn)算信號(hào)),當(dāng)一個(gè)操作產(chǎn)生溢出時(shí),將會(huì)使用有符號(hào)的無(wú)窮大來(lái)表示,如果某個(gè)操作結(jié)果沒(méi)有明確的數(shù)學(xué)定義的話,將會(huì)使用NaN值來(lái)表示。所有使用NaN值作為操作數(shù)的算術(shù)操作,結(jié)果都會(huì)返回NaN。
對(duì)浮點(diǎn)數(shù)值進(jìn)行比較時(shí)(dcmpg、dcmpl、fcmpg、fcmpl),虛擬機(jī)會(huì)采用IEEE 754規(guī)范所定義的無(wú)信號(hào)比較(Nonsignaling Comparisons)方式。
4.3.4 類型轉(zhuǎn)換指令類型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類型進(jìn)行相互轉(zhuǎn)換,JVM直接支持小范圍類型向大范圍類型的安全轉(zhuǎn)換,而處理大范圍類型到小范圍類型的窄化類型轉(zhuǎn)換則需要顯示地使用轉(zhuǎn)換指令來(lái)完成,這些指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。
窄化類型轉(zhuǎn)換會(huì)導(dǎo)致結(jié)果產(chǎn)生不同的正負(fù)號(hào)、不同的數(shù)量級(jí)、數(shù)值精度丟失的情況,但永遠(yuǎn)不可能拋出運(yùn)行時(shí)異常。
類實(shí)例與數(shù)組都屬于對(duì)象,但是其創(chuàng)建與操作使用了不同的字節(jié)碼指令,指令如下:
創(chuàng)建類實(shí)例:new
創(chuàng)建數(shù)組:newarray, anewarray, multianewarray
訪問(wèn)類字段(static字段)和實(shí)例字段:getfield, putfield, getstatic, putstatic
把一個(gè)數(shù)組元素加載到操作數(shù)棧:baload, caload, saload, iaload, laload, faload, etc.
把一個(gè)操作數(shù)棧的值存儲(chǔ)到數(shù)組元素中:bastore, castore, sastore, iastore, etc.
取數(shù)組長(zhǎng)度:arraylength
檢查類實(shí)例類型:instanceof, checkcast
4.3.6 操作數(shù)棧管理指令如同操作一個(gè)普通數(shù)據(jù)結(jié)構(gòu)中的堆棧那樣,Java虛擬機(jī)提供了一些用于直接操作數(shù)棧的指令,包括:
將操作數(shù)棧的棧頂一個(gè)或兩個(gè)元素出棧:pop、pop2
復(fù)制棧頂一個(gè)或兩個(gè)數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
將棧最頂端的兩個(gè)數(shù)值互換:swap
4.3.7 控制轉(zhuǎn)移指令控制轉(zhuǎn)移指令可以讓Java虛擬機(jī)有條件或無(wú)條件的從指定的位置指令而不是控制轉(zhuǎn)移指令的下一條指令繼續(xù)執(zhí)行程序,從概念模型上理解,可以認(rèn)為控制轉(zhuǎn)移指令就是在有條件或無(wú)條件的修改PC寄存器的值??刂妻D(zhuǎn)移指令如下。
條件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。
復(fù)合條件分支:tableswitch、lookupswitch。
無(wú)條件分支:goto、goto_w、jsr、jsr_w、ret。
int、reference、null指令集:在Java虛擬機(jī)中有專門的指令集用來(lái)處理int和reference類型的條件分支比較操作;為了可以無(wú)需明顯標(biāo)識(shí)一個(gè)實(shí)體值是否null,也有專門的指令用來(lái)檢測(cè)null值。
轉(zhuǎn)化成int類型:與算術(shù)運(yùn)算時(shí)的規(guī)則一致,對(duì)于boolean類型、byte類型、char類型和short類型的條件分支比較操作,則會(huì)先執(zhí)行相應(yīng)類型的比較運(yùn)算指令(dcmpg、dcmpl、fcmpg、fcmpl、lcmp),運(yùn)算指令會(huì)返回一個(gè)整形值到操作數(shù)棧中,隨后再執(zhí)行int類型的條件分支比較操作來(lái)完成整個(gè)分支跳轉(zhuǎn)。由于各種類型的比較最終都會(huì)轉(zhuǎn)化為int類型的比較操作,int類型比較是否方便完善就顯得尤為重要,所以Java虛擬機(jī)提供的int類型的條件分支指令是最為豐富和強(qiáng)大的。
方法調(diào)用指令與數(shù)據(jù)類型無(wú)關(guān),而方法返回指令是根據(jù)返回值的類型區(qū)分的,包括ireturn(當(dāng)返回值是boolean、byte、char、short和int類型時(shí)使用)、lreturn、freturn、dreturn和areturn;另外還有一條return指令供聲明為void的方法、實(shí)例初始化方法以及類和接口的類初始化方法使用。以下列舉了5條用于方法調(diào)用的指令:
invokevirtual——指令用于調(diào)用對(duì)象的實(shí)例方法,根據(jù)對(duì)象的實(shí)際類型進(jìn)行分派(虛方法分派),這也是Java語(yǔ)言中最常見(jiàn)的方法分派方式。
invokeinterface——指令用于調(diào)用接口方法,他會(huì)在運(yùn)行時(shí)搜索一個(gè)實(shí)現(xiàn)了這個(gè)接口方法的對(duì)象,找出適合的方法進(jìn)行調(diào)用。
invokespecial——指令用于調(diào)用一些需要特殊處理的實(shí)例方法,包括實(shí)例初始化方法、私有方法和父類方法。
invokestatic——指令用于調(diào)用類方法(static方法)。
invokedynamic——指令用于運(yùn)算時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,并執(zhí)行該方法,前面4條調(diào)用指令的分派邏輯都固化在Java虛擬機(jī)內(nèi)部,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的。
4.3.9 異常處理指令在Java程序中顯示拋出異常的操作(throw 語(yǔ)句)都由athrow指令來(lái)實(shí)現(xiàn)
除了用throw語(yǔ)句顯式拋出異常情況之外,Java虛擬機(jī)規(guī)范還規(guī)定了許多運(yùn)行時(shí)異常會(huì)在其他Java虛擬機(jī)指令檢測(cè)到異常狀況時(shí)自動(dòng)拋出。
在Java虛擬機(jī)中,處理異常(catch語(yǔ)句)不是由字節(jié)碼指令來(lái)實(shí)現(xiàn)的(很久之前曾經(jīng)使用jsr和ret指令來(lái)實(shí)現(xiàn),現(xiàn)在已經(jīng)不用了),而是采用異常表來(lái)完成的。
4.3.10 同步指令Java虛擬機(jī)可以支持方法級(jí)的同步和方法內(nèi)部一段指令序列的同步,這兩種同步結(jié)構(gòu)是使用管程(Monitor)來(lái)支持的。
方法級(jí)的同步是隱式的,即無(wú)需通過(guò)字節(jié)碼指令來(lái)控制,他實(shí)現(xiàn)在方法調(diào)用和返回操作之中。
虛擬機(jī)可以從方法常量池的方法表結(jié)構(gòu)中的ACC_SYNCHRONIZED訪問(wèn)標(biāo)志得知一個(gè)方法是否聲明為同步方法。
當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的ACC_SYNCHRONIZED訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程就要求先成功持有管程,然后才能執(zhí)行方法,最后當(dāng)方法完成(無(wú)論是正常完成還是非正常完成)時(shí)釋放管程。
在方法執(zhí)行期間,執(zhí)行線程持有了管程,其他任何線程都無(wú)法再獲取到同一個(gè)管程。如果一個(gè)同步方法執(zhí)行期間拋出了異常,并且在方法內(nèi)部無(wú)法處理此異常,那么這個(gè)同步方法所持有的管程將在異常拋到同步方法之外時(shí)自動(dòng)釋放。
同步一段指令集通常是由Java語(yǔ)言中的synchronized語(yǔ)句塊來(lái)表示的。
Java虛擬機(jī)的指令集中有monitorenter和monitorexit兩條指令來(lái)支持synchronized關(guān)鍵字的語(yǔ)義,正確實(shí)現(xiàn)synchronized關(guān)鍵字需要Javac編譯器與Java虛擬機(jī)兩者共同協(xié)作支持。
編譯器必須確保無(wú)論方法通過(guò)何種方式完成,方法中調(diào)用過(guò)的每條monitorenter指令都必須執(zhí)行其對(duì)應(yīng)的monitorexit指令,而無(wú)論這個(gè)方法是正常結(jié)束還是異常結(jié)束。
為了保證在方法異常完成時(shí)monitorenter和monoitorexit指令依然剋有正確配對(duì)執(zhí)行,編譯器會(huì)自動(dòng)產(chǎn)生一個(gè)異常處理器,這個(gè)異常處理器聲明可處理所有的異常,他的目的就是用來(lái)執(zhí)行monitorexit指令。
4.4 公有設(shè)計(jì)和私有實(shí)現(xiàn)管程:管程可以看做一個(gè)軟件模塊,它是將共享的變量和對(duì)于這些共享變量的操作封裝起來(lái),形成一個(gè)具有一定接口的功能模塊,進(jìn)程可以調(diào)用管程來(lái)實(shí)現(xiàn)進(jìn)程級(jí)別的并發(fā)控制。進(jìn)程只能互斥得使用管程,即當(dāng)一個(gè)進(jìn)程使用管程時(shí),另一個(gè)進(jìn)程必須等待。當(dāng)一個(gè)進(jìn)程使用完管程后,它必須釋放管程并喚醒等待管程的某一個(gè)進(jìn)程。在管程入口處的等待隊(duì)列稱為入口等待隊(duì)列,由于進(jìn)程會(huì)執(zhí)行喚醒操作,因此可能有多個(gè)等待使用管程的隊(duì)列,這樣的隊(duì)列稱為緊急隊(duì)列,它的優(yōu)先級(jí)高于等待隊(duì)列。
信號(hào)量:信號(hào)量是一種抽象數(shù)據(jù)類型,由一個(gè)整形 (sem)變量和兩個(gè)原子操作組成:
P():sem減1,如果sem<0等待,否則繼續(xù);
V():sem加1,如果sem<=0,說(shuō)明當(dāng)前有等著的進(jìn)程,喚醒掛在信號(hào)量上的等待進(jìn)程,可以是一個(gè)或多個(gè) 。
Java虛擬機(jī)規(guī)范描繪了Java虛擬機(jī)應(yīng)有的共同程序存儲(chǔ)格式:Class文件格式以及字節(jié)碼指令集。這些內(nèi)容與硬件、操作系統(tǒng)及具體的Java虛擬機(jī)實(shí)現(xiàn)之間是完全獨(dú)立的。
Java虛擬機(jī)實(shí)現(xiàn)必須能夠讀取Class文件并精確實(shí)現(xiàn)包含在其中的Java虛擬機(jī)代碼的語(yǔ)義,一個(gè)優(yōu)秀的虛擬機(jī)實(shí)現(xiàn),在滿足虛擬機(jī)規(guī)范的約束下對(duì)具體實(shí)現(xiàn)做出修改和優(yōu)化也是完全可行的,并且虛擬機(jī)規(guī)范中明確鼓勵(lì)實(shí)現(xiàn)者這樣做。只要優(yōu)化后Class文件依然可以被正確讀取,并且包含在其中的語(yǔ)義能得到完整的保持,那實(shí)現(xiàn)者就可以選擇任何方式去實(shí)現(xiàn)這些語(yǔ)義。
虛擬機(jī)實(shí)現(xiàn)者可以使用這種伸縮性來(lái)讓Java虛擬機(jī)獲得更高的性能、更低的內(nèi)存消耗或者更好的可移植性,選擇哪種特性取決于Java虛擬機(jī)實(shí)現(xiàn)的目標(biāo)和關(guān)注點(diǎn)是什么。虛擬機(jī)實(shí)現(xiàn)的方式主要有以下兩種:
將輸入的Java虛擬機(jī)代碼在加載或執(zhí)行時(shí)翻譯成另外一種虛擬機(jī)的指令集。
將輸入的Java虛擬機(jī)代碼在加載或執(zhí)行時(shí)翻譯成宿主CPU的本地指令集(即JIT代碼生成技術(shù))。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/72762.html
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡(jiǎn)介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁(yè)左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無(wú)意間聽(tīng)到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡(jiǎn)而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
摘要:如問(wèn)到是否使用某框架,實(shí)際是是問(wèn)該框架的使用場(chǎng)景,有什么特點(diǎn),和同類可框架對(duì)比一系列的問(wèn)題。這兩個(gè)方向的區(qū)分點(diǎn)在于工作方向的側(cè)重點(diǎn)不同。 [TOC] 這是一份來(lái)自嗶哩嗶哩的Java面試Java面試 32個(gè)核心必考點(diǎn)完全解析(完) 課程預(yù)習(xí) 1.1 課程內(nèi)容分為三個(gè)模塊 基礎(chǔ)模塊: 技術(shù)崗位與面試 計(jì)算機(jī)基礎(chǔ) JVM原理 多線程 設(shè)計(jì)模式 數(shù)據(jù)結(jié)構(gòu)與算法 應(yīng)用模塊: 常用工具集 ...
摘要:加載器種類啟動(dòng)類加載器在中用來(lái)加載自身需要的類,實(shí)現(xiàn),用來(lái)加載。那么就能保證的類會(huì)被優(yōu)先加載,限制了使用者對(duì)系統(tǒng)的影響。這種方式下就完成類加載器的雙親委派機(jī)制此處會(huì)將作為參數(shù)傳入進(jìn)去實(shí)際上是調(diào)用了方法 Class 文件的裝載流程 (類加載過(guò)程) 加載 -> 連接 (驗(yàn)證 -> 準(zhǔn)備 -> 解析) -> 初始化 -> 使用 -> 卸載 加載 加載階段,jvm 會(huì)通過(guò)類名獲取到此類的字節(jié)碼...
摘要:虛擬機(jī)包括一套字節(jié)碼指令集一組寄存器一個(gè)棧一個(gè)垃圾回收堆和一個(gè)存儲(chǔ)方法域。而使用虛擬機(jī)是實(shí)現(xiàn)這一特點(diǎn)的關(guān)鍵。虛擬機(jī)在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。此內(nèi)存區(qū)域是唯一一個(gè)在虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何情況的區(qū)域。 1、 什么是JVM? JVM是Java Virtual Machine(Java虛擬機(jī))的縮寫,JVM是一種用于計(jì)算設(shè)備的規(guī)范,它是一個(gè)虛構(gòu)出來(lái)的計(jì)算機(jī),...
摘要:我的是忙碌的一年,從年初備戰(zhàn)實(shí)習(xí)春招,年三十都在死磕源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實(shí)習(xí)。因?yàn)槲倚睦砗芮宄?,我的目?biāo)是阿里。所以在收到阿里之后的那晚,我重新規(guī)劃了接下來(lái)的學(xué)習(xí)計(jì)劃,將我的短期目標(biāo)更新成拿下阿里轉(zhuǎn)正。 我的2017是忙碌的一年,從年初備戰(zhàn)實(shí)習(xí)春招,年三十都在死磕JDK源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實(shí)習(xí)offer。然后五月懷著忐忑的心情開(kāi)始了螞蟻金...
摘要:請(qǐng)注意,我們?cè)诹牧膯卧獪y(cè)試遇到問(wèn)題多思考多查閱多驗(yàn)證,方能有所得,再勤快點(diǎn)樂(lè)于分享,才能寫出好文章。單元測(cè)試是指對(duì)軟件中的最小可測(cè)試單元進(jìn)行檢查和驗(yàn)證。 JAVA容器-自問(wèn)自答學(xué)HashMap 這次我和大家一起學(xué)習(xí)HashMap,HashMap我們?cè)诠ぷ髦薪?jīng)常會(huì)使用,而且面試中也很頻繁會(huì)問(wèn)到,因?yàn)樗锩嫣N(yùn)含著很多知識(shí)點(diǎn),可以很好的考察個(gè)人基礎(chǔ)。但一個(gè)這么重要的東西,我為什么沒(méi)有在一開(kāi)始...
閱讀 3170·2021-09-28 09:42
閱讀 3493·2021-09-22 15:21
閱讀 1166·2021-07-29 13:50
閱讀 3650·2019-08-30 15:56
閱讀 3398·2019-08-30 15:54
閱讀 1226·2019-08-30 13:12
閱讀 1208·2019-08-29 17:03
閱讀 1232·2019-08-29 10:59