亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

JVM詳解4.類文件結(jié)構(gòu)

yvonne / 2314人閱讀

摘要:類文件的結(jié)構(gòu)文件是一組以位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在文件之中,中間沒有添加任何分隔符,這使得整個文件中存儲的內(nèi)容幾乎全部是程序運行的必要數(shù)據(jù),沒有空隙存在。

點擊進(jìn)入我的博客 4.1 字節(jié)碼

平臺無關(guān):Sun公司以及其他的虛擬機提供商發(fā)布了許多可以運行在各種不同平臺上的虛擬機,這些虛擬機都可以載入和執(zhí)行同一種平臺無關(guān)的字節(jié)碼,從而實現(xiàn)了程序的“一次編寫,到處運行”。
語言無關(guān):語言無關(guān)的基礎(chǔ)是虛擬機和字節(jié)碼存儲格式,Java虛擬機不和任何語言(包括Java)綁定,它只與Class文件這種特定的二進(jìn)制文件格式所關(guān)聯(lián),Class文件中包含了Java虛擬機指令集和符號表以及若干其他輔助信息。

4.2 Class類文件的結(jié)構(gòu)

Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內(nèi)容幾乎全部是程序運行的必要數(shù)據(jù),沒有空隙存在。當(dāng)遇到需要占用8位字節(jié)以上空間的數(shù)據(jù)項時,則會按照高位在前的方式分割成若干個8位字節(jié)進(jìn)行存儲。

Class文件只有兩種數(shù)據(jù)類型:無符號數(shù)、表。

無符號數(shù):無符號數(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)成的復(fù)合數(shù)據(jù)類型,表習(xí)慣性以_info結(jié)尾。表用于描述有層次的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)上就是一張表,由以下的數(shù)據(jù)項構(gòu)成。

容量計數(shù)器:無論是無符號數(shù)還是表,當(dāng)需要描述同一類型但數(shù)量不定的多個數(shù)據(jù)時,經(jīng)常會使用一個前置的容量計數(shù)器加若干連續(xù)的數(shù)據(jù)項的形式。

4.2.1 魔數(shù)與Class文件的版本

魔數(shù):每個Class文件的頭4個字節(jié)稱為魔數(shù)(Magic Number),其唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件。值為0xCAFEBABE。
Class的版本號:緊接著魔數(shù)的4個字節(jié)存儲的是Class的版本號——第5個和第6個字節(jié)是次版本號(Minor Version),第7個和第8個字節(jié)是主版本號(Major Version)。
版本號兼容:高版本的JDK只能向下兼容以前版本的Class文件,不能運行以后版本的Class文件。

4.2.2 常量池

常量池:緊接著主次版本號后的是常量池,也可以理解為Class文件的資源倉庫,它是與其他項目關(guān)聯(lián)最多的數(shù)據(jù)類型,也是占用Class文件空間最大的數(shù)據(jù)項目之一,同時還算第一個出現(xiàn)的表類型數(shù)據(jù)項目。
常量池計數(shù)值:由于常量池中常量數(shù)量不固定,因此在入口處要放置一項u2類型的數(shù)據(jù),代表常量池計數(shù)值(從1開始,因為計數(shù)的0代表“不引用任何一個常量池項目”的含義)。
常量池存放數(shù)據(jù):常量池中主要存放兩大類常量——字面量(Literal)和符號引用(Symbolic References)。字面量比較接近于Java語言層面的常量概念——如文本字符串、聲明為final的常量值等。符號引用則屬于編譯原理方面的概念,包括下面三類常量:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符。
動態(tài)連接:Java代碼在javac編譯的時候,并沒有連接這一步驟,而是在虛擬機加載Class文件的時候動態(tài)連接。
常量池中的項:常量池中每一項都是一個表,截止到JDK 7中更用14種各不相同的表結(jié)構(gòu)數(shù)據(jù),其共同特點就是表開始的第一位是一個u1類型的標(biāo)識位。

4.2.3 訪問標(biāo)志

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

4.2.4 類索引、父類索引、接口索引

類索引和父類索引:是一個u2類型的數(shù)據(jù),用于確定這個類的全限定類名和父類的全限定類名,指向一個類型為CONSTANT_Class_info的類描述符常量,通過CONSTANT_Class_info類型的常量中的索引類型可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。
接口索引集合:是一組u2類型的數(shù)據(jù)集合,用于描述這個類實現(xiàn)了哪些接口,這些被實現(xiàn)的接口按照從左到右排列在接口索引集合中。入口的第一項——u2類型的數(shù)據(jù)為接口計數(shù)器,表示索引表的容量;如果沒有實現(xiàn)任何接口,則該計數(shù)器為0。

4.2.5 字段表集合

字段表:字段表(field_info)用于描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量,但不包括在方法內(nèi)部聲明的局部變量。
一個字段包括的信息有:字段的作用域(public、private、protected修飾符)、是實例變量還是類變量(static修飾符)、可變性(final)、并發(fā)可見性(volatile修飾符,是否強制從主內(nèi)存讀寫)、可否被被序列化(transient修飾符)、字段數(shù)據(jù)類型(基本類型、對象、數(shù)組)、字段名稱。
修飾符布爾值:上述這些信息中,各個修飾符都是布爾值,要么有某個修飾符,要么沒有,很適合使用標(biāo)志位來表示。而字段叫什么名字、字段被定義為什么數(shù)據(jù)類型,這些都是無法固定的,只能引用常量池中的常量來描述。

字段表結(jié)構(gòu)
類型 名稱 數(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)志名稱 標(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 字段是否由編譯器自動產(chǎn)生的
ACC_ENUM 0x4000 字段是否enum
name_index

name_index是對常量池的引用,代表著字段的簡單名稱。簡單名稱是指沒有類型和參數(shù)修飾的方法或者字段名稱,這個類中的inc()方法和m字段的簡單名稱分別是“inc”和“m”。
全限定名:以下面代碼為例,“org/xxx/clazz/TestClass”是這個類的全限定名,僅僅是把類全名中的“.”替換成了“/”而已,為了使連續(xù)的多個全限定名之間不產(chǎn)生混淆,在使用時最后一般會加入一個“;”表示全限定名結(jié)束。

public class TestClass {

    private int m;

    public int inc() {
        return m + 1;
    }
}
descriptor_index

descriptor_index也是對常量池的引用,代表著字段和方法的描述符。描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示。

標(biāo)識字符 含義 標(biāo)識字符 含義
B 基本類型byte J 基本類型long
C 基本類型char S 基本類型short
D 基本類型double Z 基本類型boolean
F 基本類型float V 特殊類型void
I 基本類型int L 對象類型,如Ljava/lang/Object

數(shù)組類型:每一維度將使用一個前置的“[”字符來描述,如一個定義為“java.lang.String[][]”類型的二維數(shù)組,將被記錄為:“[[Ljava/lang/String;”,,一個整型數(shù)組“int[]”被記錄為“[I”。
描述方法:按照先參數(shù)列表,后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組小括號“( )”之內(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”。

attributes_count與attribute_info

字段表都包含的固定數(shù)據(jù)項目到descriptor_index為止就結(jié)束了,不過在descriptor_index之后跟隨著一個屬性表集合用于存儲一些額外的信息,字段都可以在屬性表中描述零至多項的額外信息。對于本例中的字段m,他的屬性表計數(shù)器為0,也就是說沒有需要額外描述的信息,但是,如果將字段m的聲明改為“final static int m=123”,那就可能會存在一項名稱為ConstantValue的屬性,其值指向常量123。

字段表集合中不會列出從超類或者父接口中繼承而來的字段,但有可能列出原本Java代碼之中不存在的字段,譬如在內(nèi)部類中為了保持對外部類的訪問性,會自動添加指向外部類實例的字段。

在Java語言中字段是無法重載的,兩個字段的數(shù)據(jù)類型、修飾符不管是否相同,都必須使用不一樣的名稱,但是對于字節(jié)碼來講,如果兩個字段的描述符不一致,那字段重名就是合法的

4.2.6 方法表集合

方法表的結(jié)構(gòu)如同字段表一樣,依次包括了訪問標(biāo)志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表結(jié)合(attributes)幾項,如字段表所示。

方法訪問標(biāo)志
標(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 方法是否由編譯器自動產(chǎn)生的
方法里的代碼

方法里的Java代碼,經(jīng)過編譯器編譯成字節(jié)碼指令后,存放在方法屬性集合中一個名為“Code”的屬性里面,屬性表作為Class文件格式中最具擴(kuò)展性的一種數(shù)據(jù)項目。

重寫

與字段表集合相對應(yīng)的,如果父類方法在子類匯總沒有被重寫(Override),方法表集合中就不會出現(xiàn)來自父類的方法信息。

自動添加方法

有可能會出現(xiàn)由編譯器自動添加的方法,最典型的便是類構(gòu)造器“”方法和實例構(gòu)造器“”方法。

重載

在Java語言中,要重載(Overload)一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特征簽名,特征簽名就是一個方法中各個參數(shù)在常量池中的字段符號引用的集合,也就是因為返回值不會包含在特征簽名中,因此Java語言里面是無法僅僅依靠返回值的不同來對一個已有方法進(jìn)行重載的。但是在Class文件格式匯總,特征簽名的范圍更大一些,只要描述符不是完全一致的兩個方法也可以共存。也就是說,如果兩個方法有相同的名稱和特征簽名,但返回值不同,那么也是可以合法共存于同一個Class文件中的。

4.2.7 屬性表集合

在Class文件、字段表、方法表中都可以攜帶自己的屬性表集合,以用于描述某些場景專有的信息。與Class文件中其他的數(shù)據(jù)項目要求嚴(yán)格的順序、長度和內(nèi)容不同,屬性表集合的限制稍微寬松了一些,不再要求各個屬性表具有嚴(yán)格順序,并且只要不與已有屬性名重復(fù),任何人實現(xiàn)的編譯器都可以向?qū)傩员韺懭胱约憾x的屬性信息,Java虛擬機運行時會忽略掉他不認(rèn)識的屬性。

屬性表的結(jié)構(gòu)

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

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length
虛擬機規(guī)范預(yù)定義的屬性
屬性名稱 使用位置 含義
Code 方法表 Java代碼編譯成的字節(jié)碼指令
ConstantValue 字段表 final關(guān)鍵字定義的常量值
Deprecated 類、方法表、字段表 被聲明為deprecated的方法和字段
Exceptions 方法表 方法拋出的異常
EnclosingMethod 類文件 僅當(dāng)一個類為局部類或者匿名類時才能擁有這個屬性,這個屬性用于標(biāo)識這個類所在的外圍方法
InnerClasses 類文件 內(nèi)部類列表
LineNumberTable Code屬性 Java源碼的行號與字節(jié)碼指令的對用關(guān)系
LocalVariableTable Code屬性 方法的局部變量描述
StackMapTable Code屬性 JDK1.6中新增的屬性,供新的類型檢查驗證器(Type Checker)檢查和處理目標(biāo)方法的局部變量和操作數(shù)棧所需要的類型是否匹配
Signature 類、方法表、字段表 JDK1.5中新增的屬性,這個屬性用于支持泛型情況下的方法簽名,在Java語言中,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數(shù)化類型(Parameterized Types),則Signature屬性會為他記錄泛型簽名信息。由于Java的泛型采用擦除法實現(xiàn),在為了避免類型信息被擦出后導(dǎo)致簽名混亂,需要這個屬性記錄泛型中的相關(guān)信息
SourceFile 類文件 記錄源文件名稱
SourceDebugExtension 類文件 JDK 1.6中新增的屬性,SourceDebugExtension屬性用于存儲額外的調(diào)試信息,譬如在進(jìn)行JSP文件調(diào)試時,無法同構(gòu)Java堆棧來定位到JSP文件的行號,JSR-45規(guī)范為這些非Java語言編寫,卻需要編譯成字節(jié)碼并運行在Java虛擬機中的程序提供了一個進(jìn)行調(diào)試的標(biāo)準(zhǔn)機制,使用SourceDebugExtension屬性就可以用于存儲這個標(biāo)準(zhǔn)所新加入的調(diào)試信息
Synthetic 類、方法表、字段表 標(biāo)識方法或字段為編譯器自動生成的
LocalVariableTypeTable JDK 1.5中新增的屬性,他使用特征簽名代替描述符,是為了引入泛型語法之后能描述泛型參數(shù)化類型而添加
RuntimeVisibleAnnotations 類、方法表、字段表 JDK 1.5中新增的屬性,為動態(tài)注解提供支持。RuntimeVisibleAnnotations屬性用于指明哪些注解是運行時(實際上運行時就是進(jìn)行反射調(diào)用)可見的
RuntimeInVisibleAnnotations 類、方法表、字段表 JDK 1.5新增的屬性,與RuntimeVisibleAnnotations屬性作用剛好相反,用于指明哪些注解是運行時不可見的
RuntimeVisibleParameter Annotations 方法表 JDK 1.5新增的屬性,作用與RuntimeVisibleAnnotations屬性類似,只不過作用對象為方法參數(shù)
RuntimeInVisibleAnnotations Annotations 方法表 JDK 1.5中新增的屬性,作用與RuntimeInVisibleAnnotations屬性類似,只不過作用對象為方法參數(shù)
AnnotationDefault 方法表 JDK 1.5中新增的屬性,用于記錄注解類元素的默認(rèn)值
BootstrapMethods 類文件 JDK 1.7中新增的屬性,用于保存invokedynamic指令引用的引導(dǎo)方法限定符
Code屬性

Code屬性是Class文件中最重要的一個屬性,如果把一個Java程序中的信息分為代碼(Code,方法體里面的Java代碼)和元數(shù)據(jù)(Metadata,包括類、字段、方法定義及其他信息)兩部分,那么在整個Class文件中,Code屬性用于描述代碼,所有的其他數(shù)據(jù)項目都用于描述元數(shù)據(jù)。
Java程序方法體中的代碼經(jīng)過Javac編譯器處理后,最終變?yōu)樽止?jié)碼指令存儲在Code屬性內(nèi)。Code屬性出現(xiàn)在方法表的屬性集合之中,但并非所有的方法表都必須存在這個屬性,譬如接口或者抽象類中的方法就不存在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:是一項指向CONSTANT_Utf8_info型常量的索引,常量值固定為“Code”,他代表了該屬性的屬性名稱。

attribute_length:指示了屬性值的長度,由于屬性名稱索引與屬性長度一共為6個字節(jié),所以屬性值的長度固定為整個屬性表長度減少6個字節(jié)。

max_stack:代表了操作數(shù)棧(Operand Stacks)深度的最大值。在方法執(zhí)行的任意時刻,操作數(shù)棧都不會超過這個深度。虛擬機運行的時候需要根據(jù)這個值分配棧幀(Stack Frame)中的操作幀深度。

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可以重用,當(dāng)代碼執(zhí)行超出一個局部變量的作用域時,這個局部變量所占的Slot可以被其他局部變量所使用,Javac編譯器會根據(jù)變量的作用域來分配Slot給各個變量使用,然后計算出max_locals的大小。

code_length和code:用來存儲java源程序編譯后生成的字節(jié)碼指令。code_length代表字節(jié)碼長度,code是用于存儲字節(jié)碼指令的一系列字節(jié)流。既然叫字節(jié)碼指令,那么每個指令就是一個u1類型的單字節(jié),當(dāng)虛擬機讀取到code中的一個字節(jié)碼時,就可以對應(yīng)找出這個字節(jié)碼代表的是什么指令,并且可以知道這條指令后面是否需要跟隨參數(shù),以及參數(shù)應(yīng)當(dāng)如何理解。我們知道一個u1數(shù)據(jù)類型的取值范圍為0x00~0xFF,對應(yīng)十進(jìn)制的0~255,也就是一共可以表達(dá)256條指令,目前,Java虛擬機規(guī)范已經(jīng)定義了其中約200條編碼值對應(yīng)的指令含義。

關(guān)于code_length:有一件值得注意的事情,雖然他是一個u4類型的長度值,理論上最大值可以達(dá)到2的32次方減1,但是虛擬機規(guī)范中明確限制了一個方法不允許超過65535條字節(jié)碼指令,即他實際只使用了u2的長度,如果超過這個限制,Javac編譯器也會拒絕編譯。一般來講,編寫Java代碼時只要不是刻意去編寫一個超長的方法來為難編譯器,是不太可能超過這個最大值的限制。但是,某些特殊情況,例如在編譯一個很復(fù)雜的JSP文件時,某些JSP編譯器會把JSP內(nèi)容和頁面輸出的信息歸并于一個方法之中,就可能因為方法生成字節(jié)碼超長的原因而導(dǎo)致編譯失敗。

Exceptions屬性

這里的Exceptions屬性是在方法表與Code屬性平級的一項屬性。Exceptions屬性的作用是列舉出方法中可能拋出的受查異常(Checked Exceptions),也就是說方法描述時在throws關(guān)鍵字啊后面列舉的異常。他的結(jié)構(gòu)見下表。

類型 名稱 數(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:項表示方法可能拋出number_of_exceptions種受查異常

exception_index_table:每一種受查異常使用一個exception_index_table項表示,exception_index_table是一個指向常量池中CONSTANT_Class_info型常量的索引,代表了該受查異常的類型。

LineNumberTable屬性

LineNumberTable屬性用于描述Java源碼行號與字節(jié)碼行號(字節(jié)碼的偏移量)之間的對應(yīng)關(guān)系。他并不是運行時必須的屬性,但默認(rèn)生成到Class文件之中,可以在Javac中分別使用-g : none或-g : lines選項來取消或要求生成這項信息。如果選擇不生成LineNumberTable屬性,對程序運行產(chǎn)生的最主要的影響就是當(dāng)拋出異常時,堆棧中將不會顯示出錯的行號,并且在調(diào)試程序的時候,也無法按照源碼行來設(shè)置斷點。LineNumberTable屬性的結(jié)構(gòu)見下表。

類型 名稱 數(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:是一個數(shù)量為line_number_table_length、類型為line_number_info的集合

line_number_info表:包括了start_pc和line_number兩個u2類型的數(shù)據(jù)項,前者是字節(jié)碼行號,后者是Java源碼行號。

LocalVariableTable屬性

LocalVariableTable屬性用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關(guān)系,她也不是運行時必須的屬性,但默認(rèn)會生成到Class文件之中,可以在Javac中分別使用-g : none或-g :vars選項來取消或要求生成這項信息。如果沒有生成這項屬性,最大的影響就是當(dāng)前其他人引用這個方法時,所有的參數(shù)名稱都將會丟失,IDE將會使用諸如arg0、arg1之類的占位符代替原有的參數(shù)名,這對程序運行沒有影響,但是會對代碼編寫帶來較大不便,而且在調(diào)試期間無法根據(jù)參數(shù)名稱從上下文中獲得參數(shù)值。LocalVariableTable屬性的結(jié)構(gòu)見下表。

類型 名稱 數(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:屬性分別代表了這個局部變量的生命周期開始地字節(jié)碼偏移量及其作用范圍覆蓋的長度,兩者結(jié)合起來就是這個局部變量在字節(jié)碼之中的作用域范圍。

name_index和descriptor_index:都是指向常量池中CONSTANT_Utf8_info型常量的索引,分別代表了局部變量的名稱以及這個局部變量的描述符。

index:是這個局部變量在棧幀局部變量表中Slot的位置。當(dāng)這個變量數(shù)據(jù)類型是64位類型時(double和long),他占用的Slot為index和index+1兩個。

姐妹屬性:在JDK1.5引入泛型之后,LocalVariableTable屬性增加了一個“姐妹屬性”:LocalVariableTypeTable,這個新增的屬性結(jié)構(gòu)與LocalVariableTable非常相似,僅僅是吧記錄的字段描述符的descriptor_index替換成了字段的特征簽名(Signature),對于非泛型類型來說,描述符和特征簽名能描述的信息是基本一致的,但是泛型引入后,由于描述符中反省的參數(shù)化類型被擦除掉,描述符就不能準(zhǔn)確的描述泛型類型了,因此出現(xiàn)了LocalVariableTypeTable。

SourceFile屬性

SourceFile屬性用于記錄生成這個Class文件的源碼文件名稱。這個屬性也是可選的,可以分別使用Javac的-g:none-g: source選項來關(guān)閉或要求生成這項信息。在Java中,對于大多數(shù)的類來說,類名和文件名是一致的,但是有一些特殊情況(如內(nèi)部類)例外。如果不生成這項屬性,當(dāng)拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名。這個屬性是一個定長的屬性,其結(jié)構(gòu)見下表。

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 sourcefile_index ?

sourcefile_index數(shù)據(jù)項:是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼我呢見的文件名。

ConstantValue屬性

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

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 constantvalue_index 1

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常量中的一種。

InnerClasses屬性

InnerClasses屬性用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)。如果一個類中定義了內(nèi)部類,那編譯器將會為他以及他所包含的內(nèi)部類生成InnerClasses屬性。該屬性的結(jié)構(gòu)見下表。

類型 名稱 數(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:代表需要記錄多少個內(nèi)部類信息。

inner_classes_info表:每一個內(nèi)部類的信息都由一個inner_classes_info表進(jìn)行描述。inner_classes_info的結(jié)構(gòu)見下表。

類型 名稱 數(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型常量的索引,代表這個內(nèi)部類的名稱,如果是匿名內(nèi)部類,那么這項值為0.

inner_class_access_flags:是內(nèi)部類的訪問標(biāo)志,類似于類的access_flags,他的取值范圍見下表。

標(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)部類是否是一個注解
ACC_ENUM 0x4000 內(nèi)部類是否是一個枚舉
Deprecated及Synthetic屬性

Deprecated和Synthetic兩個屬性都屬于標(biāo)志類型的布爾屬性,只存在有和沒有的區(qū)別,沒有屬性值的概念。屬性的結(jié)構(gòu)非常簡單,其中attribute_length數(shù)據(jù)項的值必須為0x00000000,因為沒有任何屬性值需要設(shè)置,見下表:

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1

Deprecated屬性用于表示每個類、字段或者方法,已經(jīng)被程序作者定位不在推薦使用,他可以通過在代碼中使用@deprecated注釋進(jìn)行設(shè)置。

Synthetic屬性代表此字段或者方法并不是由Java源碼直接產(chǎn)生的,而是由編譯器自行添加的,在JDK 1.5之后,標(biāo)識一個類、字段或者方法是編譯器自動產(chǎn)生的,也可以設(shè)置他們訪問標(biāo)志中的ACC_SYNTHETIC標(biāo)志位,其中最典型的例子就是Bridge Method。所有由非用戶代碼產(chǎn)生的類、方法及字段都應(yīng)當(dāng)至少設(shè)置Synthetic屬性和ACC_SYNTHETIC標(biāo)志位中的一項,唯一的例外是實例構(gòu)造器“”方法和類構(gòu)造器“”方法。

???????

StackMapTable屬性

StackMapTable屬性在JDK 1.6發(fā)布周增加到了Class文件規(guī)范中,他是一個復(fù)雜的變長屬性,位于Code屬性的屬性表,這個屬性會在虛擬機類加載的字節(jié)碼驗證階段被新類型檢查驗證器(Type Checker)使用,目的在于代替以前比較消耗性能的基于數(shù)據(jù)流分析的類型推導(dǎo)驗證器。
這個類型檢查驗證器最初來源于Sheng Liang為Java ME CLDC實現(xiàn)的字節(jié)碼驗證器。新的驗證器在同樣能保證Class文件合法性的前提下,省略了在運行期通過數(shù)據(jù)流分析確認(rèn)字節(jié)碼的行為邏輯合法性的步驟,而是在編譯階段將一系列的驗證類型(Verification Types)直接記錄在Class文件之中,通過檢查這些驗證類型代替了類型推導(dǎo)過程,從而大幅提升了字節(jié)碼驗證的性能。這個驗證器在JDK 1.6中首次提供,并在JDK 1.7中強制代替原本基于類型推斷的字節(jié)碼驗證器。
StackMapTable屬性中包含零至多個棧映射棧(Stack Map Frames),每個棧映射幀都顯示或隱式的代表了一個字節(jié)碼偏移量,用于表示該執(zhí)行到該字節(jié)碼時局部變量表和操作數(shù)棧的驗證類型。類型檢查驗證器會通過檢查目標(biāo)方法的局部變量和操作數(shù)棧所需要的類型來確定一段字節(jié)碼指令是否符合邏輯約束。StackMapTable屬性的結(jié)構(gòu)見下表。

類型 名稱 數(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虛擬機規(guī)范(Java SE 7版)》明確規(guī)定:在版本號大于或等于50.0的Class文件中,如果方法的Code屬性中沒有附帶StackMapTable屬性,那就意味著他帶有一個隱式的StackMap屬性。這個StackMap屬性的作用等同于number_of_entries值為0的StackMapTable屬性。一個方法的Code屬性最多只能有一個StackMapTable屬性,否則將拋出ClassFormatError異常。

Signature屬性

Signature屬性在JDK 1.5發(fā)布后增加到了Class文件規(guī)范之中,他是一個可選的定長屬性,可以出現(xiàn)于類、屬性表和方法表結(jié)構(gòu)的屬性表中。在JDK 1.5大幅增強了Java語言的語法,在此之后,任何類、接口、初始化方法或成員的泛型簽名如果包含餓了類型變量(Type Variables)或參數(shù)化類型(Parameterized Types),則Signature屬性會為他記錄泛型簽名信息。之所以要專門使用這樣一個屬性去記錄泛型類型,是因為Java語言的泛型采用的是擦除法實現(xiàn)的偽泛型,在字節(jié)碼(Code屬性)中,泛型信息編譯(類型變量、參數(shù)化類型)之后都統(tǒng)統(tǒng)被擦除掉。使用擦除法的好處是實現(xiàn)簡單(主要修改Javac編譯器,虛擬機內(nèi)部只做了很少的改動)、非常容易實現(xiàn)Backport,運行期也能夠節(jié)省一些類型所占的內(nèi)存空間。但壞處是運行期就無法像C#等有真泛型支持的語言那樣,將泛型類型與用戶定義的普通類型同等對待,例如運行期做反射時無法獲得到泛型信息。Signature屬性就是為了彌補這個缺陷而增設(shè)的,現(xiàn)在Java的反射API能夠獲取泛型類型,最終的數(shù)據(jù)來源也就是這個屬性。Signature屬性的結(jié)構(gòu)見下表。

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 signature_index 1

其中signature_index項的值必須是一個對常量池的有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結(jié)構(gòu),表示類簽名、方法類型簽名或字段類型簽名。如果當(dāng)前的Signature屬性是類文件的屬性,則這個結(jié)構(gòu)表示類簽名,如果當(dāng)前的Signature屬性是方法表的屬性,則這個結(jié)構(gòu)表示方法類型簽名,如果當(dāng)前Signature屬性是字段表的屬性,則這個結(jié)構(gòu)表示字段類型簽名。

BootstrapMethods屬性

BootstrapMethods屬性在JDK 1.7發(fā)布后增加到了Class文件規(guī)范之中,他是一個復(fù)雜的變長屬性,位于類文件的屬性表中。這個屬性用于保存invokedynamic指令引用的引導(dǎo)方法限定符?!禞ava虛擬機規(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ī)的手段才能使用到他們,也許在不久的將來,等JSR-292更加成熟一些,這種狀況就會改變。BootstrapMethods屬性的結(jié)構(gòu)見下表。

類型 名稱 數(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:項的值給出了bootstrap_methods[]數(shù)組中的引導(dǎo)方法限定符的數(shù)量。

bootstrap_methods[]數(shù)組:的每個成員包含了一個指向常量池CONSTANT_MethodHandle結(jié)構(gòu)的索引值,他代表了一個引導(dǎo)方法,還包含了這個引導(dǎo)方法靜態(tài)參數(shù)的序列(可能為空)。

bootstrap_method:結(jié)構(gòu)見下表。

類型 名稱 數(shù)量
u2 bootstrap_method_ref 1
u2 num_bootstrap_arguments 1
u2 bootstrap_arguments num_bootstrap_arguments

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。

4.3 字節(jié)碼指令

Java虛擬機的指令由一個字節(jié)長度的、代表著某種特定操作含義的數(shù)字(稱為操作碼,Opcode)以及跟隨其后的零至多個代表此操作所需參數(shù)(稱為操作數(shù),Operands)而構(gòu)成。由于Java虛擬機采用面向操作數(shù)棧而不是寄存器的架構(gòu),所以大多數(shù)的指令都不包含操作數(shù),只有一個操作碼。
操作碼總數(shù):Java虛擬機操作碼的長度為一個字節(jié),這意味著指令集的操作碼總數(shù)不可能超過256條
放棄操作數(shù)對齊:由于Class文件格式放棄了編譯后代碼的操作數(shù)長度對齊,這就意味著虛擬機處理那些超過一個字節(jié)數(shù)據(jù)的時候,不得不在運行時從字節(jié)中重建出具體數(shù)據(jù)的結(jié)構(gòu),如果要將一個16位長度的無符號整數(shù)使用兩個無符號字節(jié)存儲起來(將它們命名為byte1和byte2),那他們的值應(yīng)該是這樣的:

(byte1 << 8) | byte2
4.3.1 字節(jié)碼與數(shù)據(jù)類型

大多數(shù)的指令都包含了其操作所對應(yīng)的數(shù)據(jù)類型信息,iload指令用于從局部變量表中加載int型的數(shù)據(jù)到操作數(shù)棧中,而fload指令加載的則是float類型的數(shù)據(jù)。

大部分與數(shù)據(jù)類型相關(guān)的字節(jié)碼指令,他們的操作碼助記符中都有特殊的字符來表明專門為哪種數(shù)據(jù)類型服務(wù):i代表對int類型的數(shù)據(jù)操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。

有一些指令的助記符中沒有明確地指明操作類型的字母,如arraylength指令,他沒有代表數(shù)據(jù)類型的特殊字符,但操作數(shù)永遠(yuǎn)只能是一個數(shù)組類型的對象。

還有一些指令如無條件跳轉(zhuǎn)指令goto則是與數(shù)據(jù)類型無關(guān)的。

由于Java虛擬機的操作碼最多只有256個,Java虛擬機的指令被設(shè)計成非完全獨立的(Java虛擬機規(guī)范中把這種特性稱為“Not Orthogonal”,即并非每種數(shù)據(jù)類型和每一種操作都有對應(yīng)的指令)。

大部分的指令都沒有支持整數(shù)類型byte、char和short,甚至沒有任何指令支持boolean類型。編譯器會在編譯器或運行期將byte和short類型的數(shù)據(jù)帶符號擴(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ù)組時,也會轉(zhuǎn)換為使用對應(yīng)的int類型的字節(jié)碼指令來處理。因此,大多數(shù)對于boolean、byte、short和char類型數(shù)據(jù)的操作,實際上都是使用相應(yīng)的int類型作為運算類型(Computational Type)

4.3.2 加載和存儲指令

加載和存儲指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來回傳輸,這類指令包括如下內(nèi)容。

將一個局部變量加載到操作棧:

iload、iload_、lload、lload_、fload、fload_、dload、dload_、aload、aload_

將一個數(shù)值從操作數(shù)棧存儲到局部變量表:

istore、istore_、lstore、lstore_、fstore、fstore_、dstore、dstore_、astore、astore_

將一個常量加載到操作數(shù)棧:

bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_ml、iconst_、lconst_、fconst_、dconst_

擴(kuò)充局部變量表的訪問索引的指令:

wide

以尖括號結(jié)尾的(例如iload_)這些指令助記符實際上是代表了一組指令(例如iload_,他代表了iload_0、iload_1、iload_2和iload_3這幾條指令)。這幾組指令都是某個帶有一個操作數(shù)的通用指令的特殊形式。對于這若干組特殊指令來說,他們省略掉了顯示的操作數(shù),不需要進(jìn)行取操作數(shù)的動作,實際上操作數(shù)就隱含在指令中。除了這點之外,他們的語義與原生的通用指令完全一致(例如iload_0的語義與操作數(shù)為0時的iload指令語義完全一致)。

4.3.3 運算指令

運算或算術(shù)指令用于對兩個操作數(shù)棧上的值進(jìn)行某種特定運算,并把結(jié)果重新存入到操作棧頂。大體上算術(shù)指令可以分為兩種:對整型數(shù)據(jù)進(jìn)行運算的指令與對浮點型數(shù)據(jù)進(jìn)行運算的指令。由于沒有直接支持byte、short、char和boolean類型的算術(shù)指令,對于這類數(shù)據(jù)的運算,應(yīng)使用操作int類型的指令代替。整數(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ù)運算

在處理整型數(shù)據(jù)時,只有除法指令(idiv和ldiv)以及求余指令(irem和lrem)中當(dāng)出現(xiàn)除數(shù)為零時會導(dǎo)致虛擬機拋出ArithmeticException異常,其余任何整型數(shù)運算場景都不應(yīng)該拋出運行時異常。

對long類型數(shù)值進(jìn)行比較時,虛擬機采用帶符號的比較方式,而

浮點數(shù)運算

虛擬機在處理浮點數(shù)時必須嚴(yán)格遵循IEEE 754規(guī)范中所規(guī)定的行為和限制。也就是說,Java虛擬機必須完全支持IEEE 754中定義的非正規(guī)浮點數(shù)值(Denormalized Floating-Point Numbers)和逐級下溢(Gradual Underflow)的運算規(guī)則。

所有的運算結(jié)果都必須舍入到適當(dāng)?shù)木?,非精確的結(jié)果必須舍入為可被表示的最接近的精確值,如果有兩種可表示的形式與該值一樣接近,將優(yōu)先選擇最低有效位為零的。

Java虛擬機在處理浮點數(shù)運算時,不會拋出任何運行時異常(這里所講的是Java語言中的異常,勿與IEEE 754規(guī)范中的浮點異?;ハ嗷煜?,IEEE 754的浮點異常是一種運算信號),當(dāng)一個操作產(chǎn)生溢出時,將會使用有符號的無窮大來表示,如果某個操作結(jié)果沒有明確的數(shù)學(xué)定義的話,將會使用NaN值來表示。所有使用NaN值作為操作數(shù)的算術(shù)操作,結(jié)果都會返回NaN。

對浮點數(shù)值進(jìn)行比較時(dcmpg、dcmpl、fcmpg、fcmpl),虛擬機會采用IEEE 754規(guī)范所定義的無信號比較(Nonsignaling Comparisons)方式。

4.3.4 類型轉(zhuǎn)換指令

類型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類型進(jìn)行相互轉(zhuǎn)換,JVM直接支持小范圍類型向大范圍類型的安全轉(zhuǎn)換,而處理大范圍類型到小范圍類型的窄化類型轉(zhuǎn)換則需要顯示地使用轉(zhuǎn)換指令來完成,這些指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。
窄化類型轉(zhuǎn)換會導(dǎo)致結(jié)果產(chǎn)生不同的正負(fù)號、不同的數(shù)量級、數(shù)值精度丟失的情況,但永遠(yuǎn)不可能拋出運行時異常。

4.3.5 對象創(chuàng)建與訪問指令

類實例與數(shù)組都屬于對象,但是其創(chuàng)建與操作使用了不同的字節(jié)碼指令,指令如下:

創(chuàng)建類實例:new

創(chuàng)建數(shù)組:newarray, anewarray, multianewarray

訪問類字段(static字段)和實例字段:getfield, putfield, getstatic, putstatic

把一個數(shù)組元素加載到操作數(shù)棧:baload, caload, saload, iaload, laload, faload, etc.

把一個操作數(shù)棧的值存儲到數(shù)組元素中:bastore, castore, sastore, iastore, etc.

取數(shù)組長度:arraylength

檢查類實例類型:instanceof, checkcast

4.3.6 操作數(shù)棧管理指令

如同操作一個普通數(shù)據(jù)結(jié)構(gòu)中的堆棧那樣,Java虛擬機提供了一些用于直接操作數(shù)棧的指令,包括:

將操作數(shù)棧的棧頂一個或兩個元素出棧:pop、pop2

復(fù)制棧頂一個或兩個數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2

將棧最頂端的兩個數(shù)值互換:swap

4.3.7 控制轉(zhuǎn)移指令

控制轉(zhuǎn)移指令可以讓Java虛擬機有條件或無條件的從指定的位置指令而不是控制轉(zhuǎn)移指令的下一條指令繼續(xù)執(zhí)行程序,從概念模型上理解,可以認(rèn)為控制轉(zhuǎn)移指令就是在有條件或無條件的修改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。

無條件分支:goto、goto_w、jsr、jsr_w、ret。

int、reference、null指令集:在Java虛擬機中有專門的指令集用來處理int和reference類型的條件分支比較操作;為了可以無需明顯標(biāo)識一個實體值是否null,也有專門的指令用來檢測null值。
轉(zhuǎn)化成int類型:與算術(shù)運算時的規(guī)則一致,對于boolean類型、byte類型、char類型和short類型的條件分支比較操作,則會先執(zhí)行相應(yīng)類型的比較運算指令(dcmpg、dcmpl、fcmpg、fcmpl、lcmp),運算指令會返回一個整形值到操作數(shù)棧中,隨后再執(zhí)行int類型的條件分支比較操作來完成整個分支跳轉(zhuǎn)。由于各種類型的比較最終都會轉(zhuǎn)化為int類型的比較操作,int類型比較是否方便完善就顯得尤為重要,所以Java虛擬機提供的int類型的條件分支指令是最為豐富和強大的。

4.3.8 方法調(diào)用和返回指令

方法調(diào)用指令與數(shù)據(jù)類型無關(guān),而方法返回指令是根據(jù)返回值的類型區(qū)分的,包括ireturn(當(dāng)返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn和areturn;另外還有一條return指令供聲明為void的方法、實例初始化方法以及類和接口的類初始化方法使用。以下列舉了5條用于方法調(diào)用的指令:

invokevirtual——指令用于調(diào)用對象的實例方法,根據(jù)對象的實際類型進(jìn)行分派(虛方法分派),這也是Java語言中最常見的方法分派方式。

invokeinterface——指令用于調(diào)用接口方法,他會在運行時搜索一個實現(xiàn)了這個接口方法的對象,找出適合的方法進(jìn)行調(diào)用。

invokespecial——指令用于調(diào)用一些需要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法。

invokestatic——指令用于調(diào)用類方法(static方法)。

invokedynamic——指令用于運算時動態(tài)解析出調(diào)用點限定符所引用的方法,并執(zhí)行該方法,前面4條調(diào)用指令的分派邏輯都固化在Java虛擬機內(nèi)部,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的。

4.3.9 異常處理指令

在Java程序中顯示拋出異常的操作(throw 語句)都由athrow指令來實現(xiàn)

除了用throw語句顯式拋出異常情況之外,Java虛擬機規(guī)范還規(guī)定了許多運行時異常會在其他Java虛擬機指令檢測到異常狀況時自動拋出。

在Java虛擬機中,處理異常(catch語句)不是由字節(jié)碼指令來實現(xiàn)的(很久之前曾經(jīng)使用jsr和ret指令來實現(xiàn),現(xiàn)在已經(jīng)不用了),而是采用異常表來完成的。

4.3.10 同步指令

Java虛擬機可以支持方法級的同步和方法內(nèi)部一段指令序列的同步,這兩種同步結(jié)構(gòu)是使用管程(Monitor)來支持的。

方法級的同步

方法級的同步是隱式的,即無需通過字節(jié)碼指令來控制,他實現(xiàn)在方法調(diào)用和返回操作之中。

虛擬機可以從方法常量池的方法表結(jié)構(gòu)中的ACC_SYNCHRONIZED訪問標(biāo)志得知一個方法是否聲明為同步方法。

當(dāng)方法調(diào)用時,調(diào)用指令將會檢查方法的ACC_SYNCHRONIZED訪問標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程就要求先成功持有管程,然后才能執(zhí)行方法,最后當(dāng)方法完成(無論是正常完成還是非正常完成)時釋放管程。

在方法執(zhí)行期間,執(zhí)行線程持有了管程,其他任何線程都無法再獲取到同一個管程。如果一個同步方法執(zhí)行期間拋出了異常,并且在方法內(nèi)部無法處理此異常,那么這個同步方法所持有的管程將在異常拋到同步方法之外時自動釋放。

同步一段指令集

同步一段指令集通常是由Java語言中的synchronized語句塊來表示的。

Java虛擬機的指令集中有monitorenter和monitorexit兩條指令來支持synchronized關(guān)鍵字的語義,正確實現(xiàn)synchronized關(guān)鍵字需要Javac編譯器與Java虛擬機兩者共同協(xié)作支持。

編譯器必須確保無論方法通過何種方式完成,方法中調(diào)用過的每條monitorenter指令都必須執(zhí)行其對應(yīng)的monitorexit指令,而無論這個方法是正常結(jié)束還是異常結(jié)束。

為了保證在方法異常完成時monitorenter和monoitorexit指令依然剋有正確配對執(zhí)行,編譯器會自動產(chǎn)生一個異常處理器,這個異常處理器聲明可處理所有的異常,他的目的就是用來執(zhí)行monitorexit指令。

信號量與管程

管程:管程可以看做一個軟件模塊,它是將共享的變量和對于這些共享變量的操作封裝起來,形成一個具有一定接口的功能模塊,進(jìn)程可以調(diào)用管程來實現(xiàn)進(jìn)程級別的并發(fā)控制。進(jìn)程只能互斥得使用管程,即當(dāng)一個進(jìn)程使用管程時,另一個進(jìn)程必須等待。當(dāng)一個進(jìn)程使用完管程后,它必須釋放管程并喚醒等待管程的某一個進(jìn)程。在管程入口處的等待隊列稱為入口等待隊列,由于進(jìn)程會執(zhí)行喚醒操作,因此可能有多個等待使用管程的隊列,這樣的隊列稱為緊急隊列,它的優(yōu)先級高于等待隊列。

信號量:信號量是一種抽象數(shù)據(jù)類型,由一個整形 (sem)變量和兩個原子操作組成:

P():sem減1,如果sem<0等待,否則繼續(xù);

V():sem加1,如果sem<=0,說明當(dāng)前有等著的進(jìn)程,喚醒掛在信號量上的等待進(jìn)程,可以是一個或多個 。

4.4 公有設(shè)計和私有實現(xiàn)

Java虛擬機規(guī)范描繪了Java虛擬機應(yīng)有的共同程序存儲格式:Class文件格式以及字節(jié)碼指令集。這些內(nèi)容與硬件、操作系統(tǒng)及具體的Java虛擬機實現(xiàn)之間是完全獨立的。

Java虛擬機實現(xiàn)必須能夠讀取Class文件并精確實現(xiàn)包含在其中的Java虛擬機代碼的語義,一個優(yōu)秀的虛擬機實現(xiàn),在滿足虛擬機規(guī)范的約束下對具體實現(xiàn)做出修改和優(yōu)化也是完全可行的,并且虛擬機規(guī)范中明確鼓勵實現(xiàn)者這樣做。只要優(yōu)化后Class文件依然可以被正確讀取,并且包含在其中的語義能得到完整的保持,那實現(xiàn)者就可以選擇任何方式去實現(xiàn)這些語義。

虛擬機實現(xiàn)者可以使用這種伸縮性來讓Java虛擬機獲得更高的性能、更低的內(nèi)存消耗或者更好的可移植性,選擇哪種特性取決于Java虛擬機實現(xiàn)的目標(biāo)和關(guān)注點是什么。虛擬機實現(xiàn)的方式主要有以下兩種:

將輸入的Java虛擬機代碼在加載或執(zhí)行時翻譯成另外一種虛擬機的指令集。

將輸入的Java虛擬機代碼在加載或執(zhí)行時翻譯成宿主CPU的本地指令集(即JIT代碼生成技術(shù))。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/72762.html

相關(guān)文章

  • Java學(xué)習(xí)路線總結(jié),搬磚工逆襲Java架構(gòu)師(全網(wǎng)最強)

    摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號作者架構(gòu)師奮斗者掃描主頁左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點贊收藏留言前情提要無意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...

    Scorpion 評論0 收藏0
  • Java面試 32個核心必考點完全解析

    摘要:如問到是否使用某框架,實際是是問該框架的使用場景,有什么特點,和同類可框架對比一系列的問題。這兩個方向的區(qū)分點在于工作方向的側(cè)重點不同。 [TOC] 這是一份來自嗶哩嗶哩的Java面試Java面試 32個核心必考點完全解析(完) 課程預(yù)習(xí) 1.1 課程內(nèi)容分為三個模塊 基礎(chǔ)模塊: 技術(shù)崗位與面試 計算機基礎(chǔ) JVM原理 多線程 設(shè)計模式 數(shù)據(jù)結(jié)構(gòu)與算法 應(yīng)用模塊: 常用工具集 ...

    JiaXinYi 評論0 收藏0
  • JVM_加載機制詳解

    摘要:加載器種類啟動類加載器在中用來加載自身需要的類,實現(xiàn),用來加載。那么就能保證的類會被優(yōu)先加載,限制了使用者對系統(tǒng)的影響。這種方式下就完成類加載器的雙親委派機制此處會將作為參數(shù)傳入進(jìn)去實際上是調(diào)用了方法 Class 文件的裝載流程 (類加載過程) 加載 -> 連接 (驗證 -> 準(zhǔn)備 -> 解析) -> 初始化 -> 使用 -> 卸載 加載 加載階段,jvm 會通過類名獲取到此類的字節(jié)碼...

    MasonEast 評論0 收藏0
  • JVM虛擬機詳解

    摘要:虛擬機包括一套字節(jié)碼指令集一組寄存器一個棧一個垃圾回收堆和一個存儲方法域。而使用虛擬機是實現(xiàn)這一特點的關(guān)鍵。虛擬機在執(zhí)行字節(jié)碼時,把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行。此內(nèi)存區(qū)域是唯一一個在虛擬機規(guī)范中沒有規(guī)定任何情況的區(qū)域。 1、 什么是JVM?   JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用于計算設(shè)備的規(guī)范,它是一個虛構(gòu)出來的計算機,...

    rottengeek 評論0 收藏0
  • 我的阿里之路+Java面經(jīng)考點

    摘要:我的是忙碌的一年,從年初備戰(zhàn)實習(xí)春招,年三十都在死磕源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實習(xí)。因為我心理很清楚,我的目標(biāo)是阿里。所以在收到阿里之后的那晚,我重新規(guī)劃了接下來的學(xué)習(xí)計劃,將我的短期目標(biāo)更新成拿下阿里轉(zhuǎn)正。 我的2017是忙碌的一年,從年初備戰(zhàn)實習(xí)春招,年三十都在死磕JDK源碼,三月份經(jīng)歷了阿里五次面試,四月順利收到實習(xí)offer。然后五月懷著忐忑的心情開始了螞蟻金...

    姘擱『 評論0 收藏0
  • Java經(jīng)典

    摘要:請注意,我們在聊聊單元測試遇到問題多思考多查閱多驗證,方能有所得,再勤快點樂于分享,才能寫出好文章。單元測試是指對軟件中的最小可測試單元進(jìn)行檢查和驗證。 JAVA容器-自問自答學(xué)HashMap 這次我和大家一起學(xué)習(xí)HashMap,HashMap我們在工作中經(jīng)常會使用,而且面試中也很頻繁會問到,因為它里面蘊含著很多知識點,可以很好的考察個人基礎(chǔ)。但一個這么重要的東西,我為什么沒有在一開始...

    xcold 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<