JVM-类文件结构
目錄
?
無關(guān)性基石
Class類文件的結(jié)構(gòu)
魔數(shù)和Class文件的版本
常量池
訪問標(biāo)識
類索引、父類索引與接口索引集合:
字段表集合
字段表結(jié)構(gòu):
字段訪問標(biāo)志
方法表集合:
方法表的結(jié)構(gòu):
屬性表集合:
虛擬機(jī)規(guī)范預(yù)定義的屬性:
Code屬性
異常表的結(jié)構(gòu):
Exception屬性
LineNumberTable屬性
LocalVariableTable屬性
SourceFile屬性
ConstantValue屬性
InnerClasses屬性
??????? InnerClasses屬性結(jié)構(gòu):
Deprecated及Synthetic屬性
StackMapTable屬性
Signature屬性
BootstrapMethods屬性
字節(jié)碼指令簡介
指令的設(shè)計
基本類型
邏輯功能
指令的理解
指令-相關(guān)計算機(jī)英語詞匯含義
指令-數(shù)據(jù)類型相關(guān)的指令
按照邏輯功能進(jìn)行劃分
無關(guān)性基石
一次編寫,到處運行(Write One,Run Anywhere)
- 平臺無關(guān)性
字節(jié)碼(ByteCode)是構(gòu)成平臺無關(guān)性的基石。
- 語言無關(guān)性
其他語言運行在JVM上,語言無關(guān)性的基礎(chǔ)仍然是虛擬機(jī)和字節(jié)碼存儲格式。
java虛擬機(jī)提供的語言無關(guān)性Class類文件的結(jié)構(gòu)
任何一個Class文件都對應(yīng)者一個類或接口的定義信息,但是,類或接口不一定定義在class文件里。
Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各項數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,如果是超過8位字節(jié)以上空間的數(shù)據(jù)項,則會按照高位在前的方式(Big-Endian)分割成若干個8位字節(jié)進(jìn)行存儲。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,Class文件格式采用一種類似于C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲的,這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號數(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é)尾。表用于描述有層次關(guān)系的復(fù)核結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)上就是一張表,它是由下表所示的數(shù)據(jù)項構(gòu)成的:
| item | 含義 |
| magic | 魔數(shù),它的唯一作用時確定這個文件能否為一個能被虛擬機(jī)接受的Class文件。許多文件標(biāo)準(zhǔn)中都使用魔數(shù)來進(jìn)行身份標(biāo)識。Class文件的魔數(shù)值為:0xCAFEBABE |
| minor_version | 這個類文件的次版本號。 |
| major_version | 這個類文件的主版本號。 |
| constant_pool_count | 常量池容器計數(shù)值,等于常量池(constant_pool)中實體數(shù)量加1。常量池的索引只有在比零大,比該值小才認(rèn)為是有效的。 |
| constant_pool[] | 常量池中的結(jié)構(gòu)表示字符串常量、類名字、接口名字、字段(fieldname)、其他常量(指向ClassFile中的其他結(jié)構(gòu)、子結(jié)構(gòu))。 |
| access_flag | 訪問標(biāo)志。用于識別一些類或接口層次的訪問信息,包括:這個Class是類還是接口;是否定義為public;是否定義為abstract類型;如果是類的話,是否被聲明為final等。 |
| this_class | 用于確定這個類的全限定名。 |
| super_class | 用于確定這個類的父類的全限定名。 |
| interfaces_count | 用于標(biāo)識類實現(xiàn)了幾個接口。 |
| interfaces[] | 類實現(xiàn)了哪些接口,按implements后的順序顯示在這個集合中。 |
| fields_count | 用于描述接口或者類中聲明的變量的數(shù)量,字段包括類級變量以及實例級變量。 |
| fields[] | 用于描述接口或者類中聲明的變量,字段包括類級變量以及實例級變量。描述信息包括:字段的作用域(public、private、protected)、實例變量還是類變量(static)、可變性(final)、并發(fā)可見性(volatile)、字段數(shù)據(jù)類型、字段名稱、可否被序列化。除字段數(shù)據(jù)類型、字段名稱需要引用常量池中常量來描述外,其余均用標(biāo)志位來表示。 |
| methods_count | 接口或類中方法的數(shù)量。 |
| methods[] | 描述接口或類中的方法。 |
魔數(shù)和Class文件的版本
每個Class文件的頭四個字節(jié)稱為魔數(shù),它的唯一作用就是確定這個文件是否為一個能被虛擬機(jī)接受的Class文件。很多文件存儲標(biāo)準(zhǔn)都是使用魔數(shù)而不是擴(kuò)展名來進(jìn)行識別,主要是基于安全方面考慮。緊接著魔數(shù)的四個字節(jié)存儲的是Class文件的版本號:第五和第六是次版本號,第七和第八是主版本號。
JDK 1.7.0的主次版本號號是 00 00 00 33,不同的jdk版本號生成的版本號不同。
在編譯時,如果帶-target參數(shù),則以此參數(shù)為準(zhǔn)。
1. 有時候我們在運行程序時會拋出這個Error 錯誤:"java.lang.UnsupportedClassVersionError: Bad version number in .class file"。上面已經(jīng)揭示了出現(xiàn)這個問題的原因,就是在于當(dāng)前嘗試加載class文件的JVM虛擬機(jī)的版本 低于class文件的版本。解決方法:1.重新使用當(dāng)前jvm編譯源代碼,然后再運行代碼;2.將當(dāng)前JVM虛擬機(jī)更新到class文件的版本。
2. 怎樣查看class文件的版本號?
?可以借助于文本編輯工具,直接查看該文件的7,8個字節(jié)的值,確定class文件是什么版本的。
當(dāng)然快捷的方式使用JDK自帶的javap工具,如當(dāng)前有Programmer.class 文件,進(jìn)入此文件所在的目錄,然后執(zhí)行 ”javap -v Programmer“
?
常量池
緊接著主次版本號的是常量池,也可以理解為Class文件的資源倉庫,它是與其他項目關(guān)聯(lián)最多的數(shù)據(jù)類型,也是占用Class文件空間最大的數(shù)據(jù)項目之一,同時還算第一個出現(xiàn)的表類型數(shù)據(jù)項目。
由于常量池中常量數(shù)量不固定,因此在入口處要放置一項u2(代表兩個字節(jié))類型的數(shù)據(jù),代表常量池計數(shù)值(從1開始),因為計數(shù)的0代表“不引用任何一個常量池項目”的含義。
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。字面量比較接近于Java語言層面的常量概念,如文本字符串、聲明為final的常量值等。符號引用則屬于編譯原理方面的概念,包括下面三類常量:
- 類和接口的全限定名(Fully Qualified Name)
- 字段的名稱和描述符(Descriptor)
- 方法的名稱和描述符
??????? 全限定名:包括完整包信息的限定名,例如”org/fenixsoft/clazz/TestClass”,
??????? 為了使多個全限定名之間不混淆,使用時最后一般加入一個”;”,表示全限定名結(jié)束。
??????? 簡單名稱:沒有類型和參數(shù)修飾的方法或者字段名稱,如:”inc()”方法和”m”變量。
??????? 相對于以上二者,方法和字段的描述符比較復(fù)雜一些,描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型和順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型以及代表無返回值的void類型都用一個大寫字符表示,而對象類型就用字符L加對象的全限定名來表示。對于數(shù)組,每一個維度前面加一個”[“來描述,例如”String[][]”類型的二位數(shù)組,則被記錄為:”[[Ljava/lang/String;”,一個”int[]”將被記錄為:”[I”。而方法的描述符,按照先參數(shù)列表,后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組小括號”()”之內(nèi)。方法int indexOf(char[] source, int sourceOffset, int target)的描述為”([CII)I”。
?
常量池中每一項常量都是一個表,在JDK1.7之后共有14種表結(jié)構(gòu),它們有一個共同的特點,就是表開始的第一位是一個u1類型的標(biāo)志位(tag,取值見下表),代表當(dāng)前這個常量屬于哪種常量類型。
| 類型 | 標(biāo)志 | 描述 |
| CONSTANT_Utf8_info | 1 | UTF-8編碼字符串 |
| CONSTANT_Integer_info | 3 | 整型字面量 |
| CONSTANT_Float_info | 4 | 浮點型字面量 |
| CONSTANT_Long_info | 5 | 長整型字面量 |
| CONSTANT_Double_info | 6 | 雙精度浮點型字面量 |
| CONSTANT_Class_info | 7 | 類或接口的符號引用 |
| CONSTANT_String_info | 8 | 字符串類型字面量 |
| CONSTANT_Fieldref_info | 9 | 字段的符號引用 |
| CONSTANT_Methodref_info | 10 | 類中方法的符號引用 |
| CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符號引用 |
| CONSTANT_NameAndType_info | 12 | 字段或方法的部分符號引用 |
| CONSTANT_MethodHandle_info | 15 | 標(biāo)識方法句柄 |
| CONSTANT_MethodType_info | 16 | 標(biāo)識方法類型 |
| CONSTANT_InvokeDtnamic_info | 18 | 表示一個動態(tài)方法調(diào)用點 |
這14種常量類型各自有自己的結(jié)構(gòu),下面列出每個常量項的結(jié)構(gòu)及含義
常量池中的14種常量項的結(jié)構(gòu)總表:
?
訪問標(biāo)識
緊接著常量池之后的兩個字節(jié)代表訪問標(biāo)志(access_flags),用于識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口、是否為public類型、是否為abstract類型、類是否聲明為final等。標(biāo)志位及其含義如下表:
| 標(biāo)志名稱 | 標(biāo)志值 | 含義 |
| ACC_PUBLIC | 0X0001 | 是否為public類型 |
| ACC_FINAL | 0X0010 | 是否被聲明為final,只有類可以設(shè)置 |
| ACC_SUPER | 0X0020 | 是否允許使用invokespecial字節(jié)碼指令的新語意,invokespecial指令的語意在JDK1.0.2發(fā)生過改變,為了區(qū)別這條指令使用哪種語意,JDK1.0.2之后編譯 |
| ACC_INTERFACE | 0X0200 | 標(biāo)志這是一個接口 |
| ACC_ABSTRACT | 0X0400 | 是否為abstract類型,對于接口或者抽象類來說,此標(biāo)志值為真,其他類為假 |
| ACC_SYNTHETIC | 0X1000 | 標(biāo)志這個類并非由用戶代碼產(chǎn)生的 |
| ACC_ANNOTATION | 0X2000 | 標(biāo)志這是一個注解 |
| ACC_ENUM | 0X4000 | 標(biāo)志這是一個枚舉 |
access_flags中一共有16個標(biāo)志位可以使用,當(dāng)前只定義了其中8個,沒用使用到的標(biāo)志位要求一律為0。 access_flages的值即為類滿足上表中的值做或運算得到的值;
類索引、父類索引與接口索引集合:
訪問標(biāo)志之后順序排列類索引、父類索引、接口索引集合。Class文件中由這三項數(shù)據(jù)來確定這個類的繼承關(guān)系。
- 類索引用于確定這個類的全限定名
this_class的值必須是對constant_pool表中項目的一個有效索引值。constant_pool表在這個索引處的項必須為CONSTANT_Class_info 類型常量,表示這個 Class 文件所定義的類或接口。
- 父類索引用于確定這個類的父類的全限定名
父類索引,對于類來說,super_class 的值必須為 0 或者是對constant_pool 表中項目的一個有效索引值。如果它的值不為 0,那 constant_pool 表在這個索引處的項必須為CONSTANT_Class_info 類型常量,表示這個 Class 文件所定義的類的直接父類。當(dāng)前類的直接父類,以及它所有間接父類的access_flag 中都不能帶有ACC_FINAL 標(biāo)記。對于接口來說,它的Class文件的super_class項的值必須是對constant_pool表中項目的一個有效索引值。constant_pool表在這個索引處的項必須為代表 java.lang.Object 的 CONSTANT_Class_info 類型常量 。如果 Class 文件的 super_class的值為 0,那這個Class文件只可能是定義的是java.lang.Object類,只有它是唯一沒有父類的類。
- 接口索引集合用來描述這個類實現(xiàn)了哪些接口。
接口索引集合入口第一項是u2類型的接口計數(shù)器(interfaces_count)表示索引表的容量(即實現(xiàn)了幾個接口)。如果該類沒用實現(xiàn)任何接口,則計數(shù)器值為0,后面的接口索引表不再占用任何字節(jié)。
類索引查找全限定名的過程:
字段表集合
排在接口索引集合后邊的是字段計數(shù)器:用于標(biāo)識有多少個字段;接著就是字段表集合。
字段表(field_info)用于描述接口或者類中聲明的變量。
字段包括類級變量以及實例級變量。可以包括的信息有:
- 字段的作用域(public、private、protected修飾符)
- 實例變量還是類變量(static修飾符)
- 可變性(final)
- 并發(fā)可見性(volatile)
- 可否被序列化(transient)
- 字段數(shù)據(jù)類型(基本類型,對象,數(shù)組)
- 字段名稱
?
字段表結(jié)構(gòu):
| 類型 | 名稱 | 數(shù)量 |
| u2 | access_flags | 1 |
| u2 | name_index | 1 |
| u2 | descriptor_index | 1 |
| u2 | attribute_count | 1 |
| attribute_info | attributes | attribute_count |
字段修飾符放在access_flags項目中,它與類中的access_flags項目非常相似,都是一個u2的數(shù)據(jù)類型,可以設(shè)值的標(biāo)志位和含義見下表
字段訪問標(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 | 0X0100 | 字段是否由編譯器自動產(chǎn)生的 |
| ACC_ENUM | 0X0400 | 字段是否enum |
跟隨access_flags標(biāo)志的是兩項索引值:name_index和descriptor_index。它們都是對常量池的引用,分別代表字段的簡單名稱以及字段和方法的描述符。 描述符的作用是描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型及順序)和返回值。根據(jù)描述符的規(guī)則,基本數(shù)據(jù)類型以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名表示,見下表
?
| 標(biāo)識字符 | 含義 |
| B | 基本類型byte |
| C | 基本類型char |
| D | 基本類型double |
| F | 基本類型float |
| I | 基本類型int |
| J | 基本類型long |
| S | 基本類型short |
| Z | 基本類型boolean |
| V | 特殊類型void |
| L | 對象類型。如Ljava/lang/Object;??? 對象類型后加分號,分隔。 |
對于數(shù)組類型,每一維度將使用一個前置的“[”字符來描述,如“String[][]”,會被記錄為"[[Ljava/lang/String","int[]"被記錄為“[I”。
描述符描述方法時,按照先參數(shù)列表,后返回值的順序描述。參數(shù)列表按照參數(shù)的嚴(yán)格順序放置一組小括號“()”內(nèi),如void inc()的描述符為“()V”,“viod main(String[] args)”的描述符為“([Ljava/lang/String;)V”,“int indexOf(char[] source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromIndex)”的描述符為“([CII[CIII)I”。
字段表都包含的固定數(shù)據(jù)項到descriptor_index為止就結(jié)束了,不過在descriptor_index之后跟隨著一個屬性表集合用于存儲一些額外的信息,字段都可以在屬性表中描述零至多項的額外信息。有關(guān)屬性表的介紹會在后邊具體講解。
字段表集合中不會列出從超類或者父類接口中繼承而來的字段,但有可能列出原本Java代碼之中不存在的字段。譬如在內(nèi)部類中為了保持對外部類的訪問性,會自動添加指向外部類實例字段。
方法表集合:
跟在字段表集合后的是方法計數(shù)器:用于標(biāo)識有多少個方法;緊接著的就是方法表集合。
Class文件存儲格式中對方法的描述與對字段的描述幾乎采用完全一致的方式。
方法表的結(jié)構(gòu):
| 類型 | 名稱 | 數(shù)量 |
| u2 | access_flags | 1 |
| u2 | name_index | 1 |
| u2 | descriptor_index | 1 |
| u2 | attribute_count | 1 |
| attribute_info | attributes | attribute_count |
方法表所包含的數(shù)據(jù)項目的含義也和字段表集合的非常的類似,僅在訪問標(biāo)志和屬性表集合的可選項中有所區(qū)別。由于volatile,transient關(guān)鍵字不能修飾方法,同時synchronized、native、strictfp和abstract關(guān)鍵字可以修飾方法。對于方法表,所有標(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)生 |
?
通過訪問標(biāo)志、名稱索引、描述符索引可清楚的表達(dá)方法的定義。那方法里面的代碼去哪里了呢?方法里的Java代碼經(jīng)過編譯器編譯成字節(jié)碼指令后,存放在方法屬性表集合中的“Code”屬性中。屬性表是Class文件格式中最具擴(kuò)展性的一種數(shù)據(jù)項目。
與字段表集合相對應(yīng)的,如果父類方法在子類中沒有被重寫(Override),方法表集合中就不會出現(xiàn)來自父類的方法信息,但可能出現(xiàn)編譯器自動添加的方法,最典型的便是類構(gòu)造器“<clinit>”方法和實例構(gòu)造器"<init>"方法。
在Java語言中,重載(Overload)一個方法:
1、要與原方法具有相同的簡單名稱。
2、要與原方法有不同的特征簽名。
Java代碼的方法特征簽名只包括方法名稱、參數(shù)順序及參數(shù)類型;而字節(jié)碼的特征簽名還包括方法返回值以及受查異常表。
屬性表集合:
在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,用于描述某些場景專有的信息。
與Class文件中其他的數(shù)據(jù)項目要求嚴(yán)格的順序、長度和內(nèi)容不同,屬性表集合的限制稍微寬松了一些,不再要求各個屬性表具有嚴(yán)格順序,并且只要不與已有屬性名重復(fù),任何人實現(xiàn)的編譯器都可以想屬性表中寫入自己定義的屬性信息,Java虛擬機(jī)運行時會忽略掉它不認(rèn)識的屬性。最新的《Java虛擬機(jī)規(guī)范(Java SE 7)》版中,屬性項已經(jīng)增加到21項。下邊將介紹一些關(guān)鍵常用的屬性。
虛擬機(jī)規(guī)范預(yù)定義的屬性:
| 屬性名稱 | 使用位置 | 含義 |
| Code | 方法表 | Java代碼編譯成的字節(jié)碼指令 |
| ConstantValue | 字段表 | final關(guān)鍵字定義的常量值 |
| Deprecated | 類、方法表、字段表 | 被聲明為deprecated的方法和字段 |
| Exceptions | 方法表 | 方法拋出的異常 |
| EnclosingMethod | 類文件 | 僅當(dāng)一個類為局部類或者匿名類時才能擁有這個屬性,這個屬性用于標(biāo)識這個類所在的外圍方法 |
| InnerClasses | 類文件 | 內(nèi)部類列表 |
| LineNumberTable | Code屬性 | Java源碼的行號與字節(jié)碼指令的對應(yīng)關(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 | 類文件 | JDK1.6中新增的屬性,SourceDebugExtension屬性用于存儲額外的調(diào)試信息。譬如在進(jìn)行JSP文件調(diào)試時,無法通過Java堆棧來定位JSP文件的行號,JSR-45規(guī)范為這些非Java語言編寫,卻需要編譯成字節(jié)碼并運行在Java虛擬機(jī)中的程序提供了一個進(jìn)行調(diào)試的標(biāo)準(zhǔn)機(jī)制,使用SourceDebugExtension屬性就可以用于存儲這個標(biāo)準(zhǔn)所新加入的調(diào)試信息 |
| Synthetic | 類、方法表、字段表 | 標(biāo)識方法或字段為編譯器自動生成的 |
| LocalVariableTypeTable | 類 | JDK1.5中新增的屬性,它使用特征簽名代替描述符,是為了引入泛型語法之后能描述泛型參數(shù)化類型而添加 |
| RuntimeVisibleAnnotations | 類、方法表、字段表 | JDK1.5新增的屬性,為動態(tài)注解提供支持。RuntimeVisibleAnnotations屬性用于注明哪些注解是運行時(實際上運行時就是進(jìn)行反射調(diào)用)可見的 |
| RuntimeInvisibleAnnotations | 類、方法表、字段表 | JDK1.5新增的屬性,與RuntimeVisibleAnnotations屬性作用剛好相反,用于指明哪些注解是運行時不可見的 |
| RuntimeVisibleParameterAnnotations | 方法表 | JDK1.5新增的屬性,作用與RuntimeVisibleAnnotations屬性類似,只不過作用對象為方法參數(shù) |
| RuntimeInvisibleParameterAnnotations | 方法表 | JDK1.5新增的屬性,作用與RuntimeInvisibleAnnotations屬性類似,只不過作用對象為方法參數(shù) |
| AnnotationDefault | 方法表 | JDK1.5新增的屬性,用于記錄注解類元素的默認(rèn)值 |
| BootstrapMethods | 類文件 | JDK1.7中新增的屬性,用于保存invokedynamic指令引用的引導(dǎo)方法限定符 |
對于每個屬性,它的名稱需要從常量池引用一個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 |
Code屬性
Java程序方法體中的代碼經(jīng)過Javac編譯處理后,最終變?yōu)?strong>字節(jié)碼指令存儲在Code屬性中,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 | attribute_count | ? |
| attribute_info | attributes | attribute_count |
attribute_name_index所指向的CONSTANT_Utf8_info類型常量的值固定為“Code”。
attribute_length標(biāo)識屬性值的總長度。
max_stack代表了操作數(shù)棧(Operand Stacks)深度的最大值。
max_locals代表了局部變量所表示的存儲空間(單位是Slot),一個Slot占用32個字節(jié),double或long這種64位的數(shù)據(jù)類型則需要兩個Slot來存放。方法參數(shù)、局部變量、異常變量都需要使用局部變量表來存放。Javac編譯器會根據(jù)變量的作用域來分配Slot,每個Slot在整個線程周期可以重復(fù)使用,然后根據(jù)變量數(shù)和作用域計算出max_locals的大小。
code_length和code是用來存儲Java源程序編譯后產(chǎn)生的字節(jié)碼指令,code_length代表字節(jié)碼長度,既然叫字節(jié)碼,每個指令就是一個u1類型的單字節(jié),當(dāng)虛擬機(jī)讀取到code中的一個字節(jié)碼時,就可以找出這個字節(jié)碼代表的是什么指令,并且可以知道這條指令后面是否需要跟隨參數(shù),以及參數(shù)應(yīng)當(dāng)如何理解。一個字節(jié)取值范圍為0~255,所以字節(jié)碼指令肯定不會超過256個指令,目前Java虛擬機(jī)規(guī)范定義了其中約200條編碼值對應(yīng)指令的含義。
因為code_length是一個u4類型,所以理論上每個方法的字節(jié)長度不能超過2^23-1,但是虛擬機(jī)規(guī)范中明確限定了一個方法不能超過65535條字節(jié)碼指令,即實際只用到了u2的長度。關(guān)于虛擬機(jī)字節(jié)碼執(zhí)行的講解將在下一篇博客中詳解。
?
在字節(jié)碼指令之后的是這個方法的顯式異常處理表(下文簡稱異常表)集合,異常表對于Code屬性來說并不是必須存在的。
異常表的結(jié)構(gòu):
| 類型 | 名稱 | 數(shù)量 |
| u2 | start_pc | 1 |
| u2 | end_pc | 1 |
| u2 | handle_pc | 1 |
| u2 | catch_type | 1 |
這些字段的含義是如果當(dāng)字節(jié)碼在第start_pc行到end_pc行之間(不含第end_pc行)出現(xiàn)了類型為catch_type或其子類異常(catch_type為指向一個CONSTANT_Class_info型常量的索引),則轉(zhuǎn)到第handler_pc行繼續(xù)處理。當(dāng)catch_type的值為0時,代表任意異常情況都需要轉(zhuǎn)向到handler_pc處進(jìn)行處理。
編譯器使用異常表而不是簡單的跳轉(zhuǎn)命令來實現(xiàn)Java異常及finally處理機(jī)制;在JDK1.4.2之前的Javac編譯器采用了jsr和ret指令實現(xiàn)finally語句,但在1.4.2之后已經(jīng)改為編譯器自動在每段可能的分支路徑之后都將finally語句塊的內(nèi)容冗余生成一遍來實現(xiàn)finally語義;在1.7中已經(jīng)完全禁止jsr和ret指令,如果遇到這兩條指令,虛擬機(jī)會在類加載的字節(jié)碼校驗階段拋出異常。
?
下面舉一個異常表的例子(出自深入理解Java虛擬機(jī)一書):
public class TestClass {public int inc() {int x;try{x = 1;return x;}catch(Exception e){x = 2;return x;}finally{x = 3;}} }?
編譯后的字節(jié)碼及異常表:
Code: Stack=1, Locals=5, Args_size=1 0: iconst_1 //try塊中的x=1 1: istore_1 2: iload_1 //保存x到returnValue中,此時x=1 3: istore 4 5: iconst_3 //finaly塊中的x=3 6: istore_1 7: iload 4 //將returnValue中的值放到棧頂,準(zhǔn)備給ireturn返回 9: ireturn //返回方法的int元素(返回棧頂元素1) 10: astore_2 //給catch中定義的Exception e賦值,存儲在Slot 2中 11: iconst_2 //catch塊中的x=2 12: istore_1 13: iload_1 //保存x到returnValue中,此時x=2 14: istore 4 16: iconst_3 //finally塊中的x=3 17: istore_1 18: iload 4 //將returnValue中的值放到棧頂,準(zhǔn)備給ireturn返回 20: ireturn //返回方法的int元素(返回棧頂元素2) 21: astore_3 //如果出現(xiàn)了不屬于Exception及其子類的異常才會走到這里 22: iconst_3 //finally塊中的x=3 23: istore_1 24: aload_3 //將異常放置到棧頂 25: athrow //拋出異常 Exception table: from to target type 5 10 Class java/lang/Exception //第0到第5行如果拋出Exception異常則跳轉(zhuǎn)到第10行 5 21 any //第0到第5行如果拋出任何異常則跳轉(zhuǎn)到第21行 16 21 any //第10到第16行如果拋出任何異常則跳轉(zhuǎn)到第21行
編譯器為這段Java源碼生成了3條異常表記錄,對應(yīng)3條可能出現(xiàn)的代碼執(zhí)行路徑。從Java代碼的語義上講,這3條執(zhí)行路徑分別為:
- 如果try語句塊中出現(xiàn)屬于Exception或其子類的異常,則轉(zhuǎn)到catch語句塊處理。
- 如果try語句塊中出現(xiàn)不屬于Exception或其子類的異常,則轉(zhuǎn)到finally語句塊處理。
- 如果catch語句塊中出現(xiàn)任何異常,則轉(zhuǎn)到finally語句塊處理。
字節(jié)碼0~4行所做的操作數(shù)就是將整數(shù)1賦值給變量x,并將此時x的值復(fù)制一份到最后一個本地變量表的Slot中(這個Slot里面的值在ireturn指令執(zhí)行前將會被重新讀到棧頂,作為方法返回值使用,這里暫且就記為returnValue)。如果這時沒有出現(xiàn)異常,則會繼續(xù)走到第5~9行,將變量x賦值為3,然后將之前保存到returnValue中的整數(shù)1讀入到操作棧頂,最后ireturn指令會以int形式放回操作棧頂中的值,方法結(jié)束。如果出現(xiàn)了異常,PC寄存器指針轉(zhuǎn)到第10行,第10~20行所做的事情是將2賦值給變量x,然后將變量x此時的賦值給returnValue,最后再將變量x的值改為3。方法返回前同樣將returnValue中保留的整數(shù)2讀到操作棧頂。從第21行開始的代碼,作用是將變量x的值賦為3,并將棧頂?shù)漠惓伋?#xff0c;方法結(jié)束。
Exception屬性
這里的Exception屬性是在方法表中與Code屬性平級的一項屬性,不要與前面剛剛講解完的異常表產(chǎn)生混淆。Exception屬性的作用是列舉出方法中可能拋出的受查異常(Checked Exceptions), 也就是方法描述時在throws關(guān)鍵字后面列舉的異常。
Exception屬性表結(jié)構(gòu):
| 類型 | 名稱 | 數(shù)量 |
| u2 | attribute_name_index | 1 |
| u4 | attribute_length | 1 |
| u2 | number_of_exceptions | 1 |
| u2 | exception_index_table | number_of_exceptions |
number_of_exceptions表示方法可能拋出number_of_exceptions種受查異常,每一種受查異常使用一個exception_index_table項表示,exception_index_table是一個指向常量池中CONSTANT_Class_info型常量的索引,代表該受查異常的類型。
LineNumberTable屬性
LineNumberTable屬性用于描述Java源碼行號與字節(jié)碼行號(字節(jié)碼的偏移量)之間的對應(yīng)關(guān)系。
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)系。
LocalVariableTable屬性結(jié)構(gòu):
| 類型 | 名稱 | 數(shù)量 |
| u2 | attribute_name_index | 1 |
| u4 | attribute_length | 1 |
| u2 | local_varible_table_length | 1 |
| local_variable_info | local_variable_table | local_varible_table_length |
local_variable_info項目代表了一個棧幀與源碼中的局部變量的關(guān)聯(lián),結(jié)構(gòu)見下表:
| 類型 | 名稱 | 數(shù)量 |
| 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),對于非泛型來說,描述符和特征簽名能描述的信息是基本一致的。
SourceFile屬性
SourceFile屬性用于記錄生成這個Class文件的源碼文件名稱。
sourceFile屬性結(jié)構(gòu):
| 類型 | 名稱 | 數(shù)量 |
| u2 | attribute_name_index | 1 |
| u4 | attribute_length | 1 |
| u2 | sourcefile_index | 1 |
sourcefile_index數(shù)據(jù)項是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼文件的文件名。
ConstantValue屬性
ConstantValue屬性的作用是通知虛擬機(jī)自動為靜態(tài)變量賦值。只有被static關(guān)鍵字修飾的常量(類變量)才可以使用這項屬性。目前Sun Javac編譯器的選擇是:如果同時使用final和static來修飾一個變量,并且這個變量的數(shù)據(jù)類型是基本類型或者java.lang.String的話,就生成ConstantValue屬性來進(jìn)行初始化,如果這個變量沒有被final修飾,或者并非基本類型及字符串,則將會選擇在<clinit>方法中進(jìn)行初始化。
對ConstantValue的屬性值只能限于基本類型和String。
ConstantValue屬性結(jié)構(gòu):
| 類型 | 名稱 | 數(shù)量 |
| u2 | attribute_name_index | 1 |
| u4 | attribute_length | 1 |
| u2 | constantvalue_index | 1 |
InnerClasses屬性
InnerClasses屬性用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)。
InnerClasses屬性結(jié)構(gòu):
| 類型 | 名稱 | 數(shù)量 |
| u2 | attribute_name_index | 1 |
| u4 | attribute_length | 1 |
| u2 | number_of_classes | 1 |
| inner_classes_info | inner_classes | number_of_classes |
number_of_classes代表需要記錄多少個內(nèi)部類信息。
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_flags | 1 |
inner_class_info_index和outer_class_info_index分別代表了內(nèi)部類和宿主類的符號引用。inner_name_index代表內(nèi)部類的名稱,inner_class_access_flags是內(nèi)部類的訪問標(biāo)志。
?
Deprecated及Synthetic屬性
兩個屬性都屬于標(biāo)志類型的布爾屬性,只存在有和沒有的區(qū)別,沒有屬性值的概念。Synthetic代表字段或者方法并不是有Java源碼直接產(chǎn)生的,而是由編譯器自行添加的。
StackMapTable屬性
StackMapTable屬性在JDK1.6發(fā)布后增加到了Class文件規(guī)范中,它是一個復(fù)雜的變長屬性,位于Code屬性的屬性表中。會在虛擬機(jī)類加載的字節(jié)碼驗證階段被新類型檢查驗證器(Type Checker)使用,目的在于代替以前比較消耗性能的基于數(shù)據(jù)流分析的類型推導(dǎo)驗證器。一個方法的Code屬性最多只能有一個StackMapTable屬性。
Signature屬性
Signature屬性在JDK1.5增加到Class文件規(guī)范之中,用于記錄泛型簽名信息。Java語言的泛型采用的是擦除法實現(xiàn)的偽泛型,缺點就是運行期做反射時無法獲得到泛型信息,Signature屬性就是為了彌補(bǔ)這個缺陷而增設(shè)的。
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),表示類簽名、方法類型簽名或字段類型簽名。
BootstrapMethods屬性
BootstrapMethods屬性在JDK1.7增加到Class文件規(guī)范之中的。它是一個復(fù)雜的變長屬性,位于類文件的屬性表中。用于保存invokedynamic指令引用的引導(dǎo)方法限定符。
目前Java無法生成InvokeDynamic指令和BootstrapMethods屬性。
字節(jié)碼指令簡介
Java虛擬機(jī)指令:
- 一個字節(jié)長度:導(dǎo)致最多256個指令。
- 操作碼加0或者多個操作數(shù)
指令的設(shè)計
指令的設(shè)計是邏輯功能點與數(shù)據(jù)類型的結(jié)合
基本類型
JVM共有9種基本類型,對于基本類型? 指令在設(shè)計的時候都用一個字母縮寫來指代(boolean除外)
| b | s | i | l | f | d | c | a | 無 |
邏輯功能
指令基本上就是圍繞著上面的邏輯功能以及數(shù)據(jù)類型進(jìn)行設(shè)計的
當(dāng)然也有一些并沒有明確用字母指代數(shù)據(jù)類型,比如arraylength 指令,并沒有代表數(shù)據(jù)類型的特殊字符,操作數(shù)只能是一個數(shù)組類型的對象,另外還有一些,比如無條件跳轉(zhuǎn)指令goto 則是與數(shù)據(jù)類型無關(guān)的。
指令的理解
指令-相關(guān)計算機(jī)英語詞匯含義
| push | push | 按 推動 壓入 |
| load | load | 加載 裝載 |
| const | const | 常數(shù),不變的 |
| store | store | 存儲 保存到 |
| add | add | 加法 |
| sub | subduction | 減法 |
| mul | multiplication | 乘法 |
| div | division | 除法 |
| inc | increase | 增加 |
| rem | remainder | 取余 剩下的留下的 |
| neg | negate | 取反 否定 |
| sh | shift | 移位 移動變換 |
| and | and | 與 |
| or | or | 或 |
| xor | exclusive OR | 異或 |
| 2 | to | 轉(zhuǎn)換 轉(zhuǎn)變 變成 |
| cmp | compare | 比較 |
| return | return | 返回 |
| eq | equal | 相等 |
| ne | not equal | 不相等 |
| lt | less than | 小于 |
| le | less than or equal | 小于等于 |
| gt | greater than | 大于 |
| ge | greater than or equal | 大于等于 |
| if | if | 條件判斷 如果 |
| goto | goto | 跳轉(zhuǎn) |
| invoke | invoke | 調(diào)用 |
| dup | dump | 復(fù)制 拷貝 卸下 丟下 |
指令-數(shù)據(jù)類型相關(guān)的指令
下表中最左邊一列的T表示模板,只需要用數(shù)據(jù)類型的縮寫,替換掉T 就可以得到對應(yīng)的具體的指令
如果下表中為空,說明對這種數(shù)據(jù)類型不支持這種類型的操作
| Tipush | bipush | sipush | ? | ? | ? | ? | ? | ? |
| Tconst | ? | ? | iconst | lconst | fconst | dconst | ? | aconst |
| Tload | ? | ? | iload | lload | fload | dload | ? | aload |
| Tstore | ? | ? | istore | lstore | fstore | dstore | ? | astore |
| Tinc | ? | ? | iinc | ? | ? | ? | ? | ? |
| Taload | baload | saload | iaload | laload | faload | daload | caload | aaload |
| Tastore | bastore | sastore | iastore | lastore | fastore | dastore | castore | aastore |
| Tadd | ? | ? | iadd | ladd | fadd | dadd | ? | ? |
| Tsub | ? | ? | isub | lsub | fsub | dsub | ? | ? |
| Tmul | ? | ? | imul | lmul | fmul | dmul | ? | ? |
| Tdiv | ? | ? | idiv | ldiv | fdiv | ddiv | ? | ? |
| Trem | ? | ? | irem | lrem | frem | drem | ? | ? |
| Tneg | ? | ? | ineg | lneg | fneg | dneg | ? | ? |
| Tshl | ? | ? | ishl | lshl | ? | ? | ? | ? |
| Tshr | ? | ? | ishr | lshr | ? | ? | ? | ? |
| Tushr | ? | ? | iushr | lushr | ? | ? | ? | ? |
| Tand | ? | ? | iand | land | ? | ? | ? | ? |
| Tor | ? | ? | ior | lor | ? | ? | ? | ? |
| Txor | ? | ? | ixor | lxor | ? | ? | ? | ? |
| i2T | i2b | i2s | ? | i2l | i2f | i2d | ? | ? |
| l2T | ? | ? | l2i | ? | l2f | l2d | ? | ? |
| f2T | ? | ? | f2i | f2l | ? | f2d | ? | ? |
| d2T | ? | ? | d2i | d2l | d2f | ? | ? | ? |
| Tcmp | ? | ? | ? | lcmp | ? | ? | ? | ? |
| Tcmpl | ? | ? | ? | ? | fcmpl | dcmpl | ? | ? |
| Tcmpg | ? | ? | ? | ? | fcmpg | dcmpg | ? | ? |
| if_TcmpOP | ? | ? | if_icmpOP | ? | ? | ? | ? | if_acmpOP |
| Treturn | ? | ? | ireturn | lreturn | freturn | dreturn | ? | areturn |
從上表的空白處可以看得出來:大部分?jǐn)?shù)據(jù)類型相關(guān)聯(lián)的指令,都沒有支持整數(shù)類型 byte char short ,而且沒有任何指令支持boolean類型。
因為編譯器會在編譯期或者運行期? 將byte 和short 類型的數(shù)據(jù) 帶符號擴(kuò)展 為相應(yīng)的int類型數(shù)據(jù)。
類似的,boolean 和char類型數(shù)據(jù)零位擴(kuò)展為相應(yīng)的int類型數(shù)據(jù),在處理boolean byte short char類型的數(shù)組時,也會轉(zhuǎn)換為使用對應(yīng)的int類型的字節(jié)碼指令來處理
實際類型與運算類型的對應(yīng)關(guān)系如下:
| boolean | int | 1 |
| int | int | 1 |
| byte | int | 1 |
| short | int | 1 |
| int | int | 1 |
| float | float | 1 |
| reference | reference | 1 |
| returnAddress | returnAddress | 1 |
| long | long | 2 |
| double | double | 2 ? |
按照邏輯功能進(jìn)行劃分
一、加載存儲指令
加載存儲指令用于局部變量與操作數(shù)棧交換數(shù)據(jù)以及常量裝載到操作數(shù)棧
1、將一個局部變量加載到操作棧:
iload、iload_<n>、lload、lload_<n>、fload、fload<n>、dload、dload<n>、aload、aload<n>
操作數(shù)為:局部變量的位置序號。序號從0開始 , 局部變量以slot為單位分配的。
將序號為操作數(shù)的局部變量slot 的值 加載到操作數(shù)棧。
指令可以讀作:將第(操作數(shù)+1)個 X(i l f d a)類型局部變量,推送至棧頂。ps: 操作數(shù)+1 是因為序號是從0開始的
2、將一個數(shù)值從操作數(shù)棧存儲到局部變量表:
istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>
操作數(shù)為:局部變量的位置序號。序號從0開始 , 局部變量以slot為單位分配的。
將操作數(shù)棧的值保存到序號為操作數(shù)的局部變量slot中
指令可以讀作:將棧頂 X(i l f d a)類型的數(shù)值 保存到? 第(操作數(shù)+1)個 局部變量中
3、將一個常量加載到操作數(shù)棧:
bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
操作數(shù):為將要操作的數(shù)值或者常量池行號
指令可以讀作:將類型X的值xxx 推送至棧頂? 或者是 將 行號為xxx的常量推送至棧頂
4、擴(kuò)充局部變量表的訪問索引的指令:wide
形如? xxx_<n>以尖括號結(jié)尾的代表了一組指令 (例如 iload_<n>?? 代表了iload_0? iload_1? iload_2? iload_3)
這一組指令都是某個帶有一個操作數(shù)的通用指令(例如 iload)的特殊形式
對于這些特殊形式來說,他們表面上沒有操作數(shù),但是操作數(shù)隱含在指令里面了,除此之外,語義與原指令并沒有任何的不同
(例如 iload_0? 的語義與操作數(shù)為0時的iload 語義完全相同)
- <>尖括號中的字母表示了指令隱含操作數(shù)的數(shù)據(jù)類型
- <n>表示非負(fù)整數(shù)? <i>表示int??? <l> 表示long <f> float? <d> double? 而byte char short類型數(shù)據(jù)經(jīng)常使用int來表示
- 下劃線 _?? 的后面緊跟著的值就是操作數(shù)
需要注意的是 _<n> 的形式不是無限的,對于load 和 store系列指令,對于超過4個,也就是第5個,也就是下標(biāo)是4 往后,
都是直接只用原始形式 iload 4? 不再使用_<n>的形式 所以你不會看到 load_4 load_5....或者store_4? store_5...
對于虛擬機(jī)執(zhí)行方法來說,操作數(shù)棧是工作區(qū), 所以數(shù)據(jù)的流向是對于操作數(shù)棧來說的 。load就是局部變量數(shù)據(jù)加載到操作數(shù)棧? ,store就是從操作數(shù)棧存儲到局部變量表。對于常量只有加載到操作數(shù)棧進(jìn)行使用,沒有存儲的說法。
load 和 store的操作數(shù)都是局部變量的位置,對于操作數(shù)棧與常量交換數(shù)據(jù),需要確定的是到底加載哪個值到操作數(shù)棧或者是從常量池哪行加載,所以加載常量到操作數(shù)棧的操作數(shù) 是 具體的數(shù)值 或者常量池行號。
常量加載到操作數(shù)棧比較特殊單獨說明他根據(jù)<數(shù)據(jù)類型>以及<數(shù)據(jù)的取值范圍>使用了不同的方式const指令,該系列命令主要負(fù)責(zé)把簡單的數(shù)值類型送到棧頂。比如對應(yīng)int型該方式只能把-1,0,1,2,3,4,5(分別采用iconst_m1,iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5)。
push指令
該系列命令負(fù)責(zé)把一個整型數(shù)字(長度比較小)送到到棧頂。 該系列命令有一個參數(shù),用于指定要送到棧頂?shù)臄?shù)字。 注意該系列命令只能操作一定范圍內(nèi)的整形數(shù)值,超出該范圍的使用將使用ldc命令系列。
bipush :將單字節(jié)的常量值(-128~127)推送至棧頂 0x11??????????
sipush :將一個短整型常量值(-32768~32767)推送至棧頂
ldc系列
該系列命令負(fù)責(zé)把數(shù)值常量或String常量值從常量池中推送至棧頂。
該命令后面需要給一個表示常量在常量池中位置(編號)的參數(shù) 也就是行號,
哪些常量是放在常量池呢?
比如:
final static int id=32768;?? //32767+1 就不在sipush范圍內(nèi)了
final static float double=8.8
對于const系列命令和push系列命令操作范圍之外的數(shù)值類型常量,都放在常量池中.
二、算數(shù)指令
運算后的結(jié)果自動入棧,運算或算術(shù)指令用于對兩個操作數(shù)棧上的值進(jìn)行某種特定運算,并把結(jié)果重新存入到操作棧頂.
算術(shù)指令分為兩種:
- 整型運算的指令
- 浮點型運算的指令.
無論是哪種算術(shù)指令,都使用Java虛擬機(jī)的數(shù)據(jù)類型,由于沒有直接支持byte、short、char和boolean類型的算術(shù)指令,使用操作int類型的指令代替。
- 加法指令: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
三、類型轉(zhuǎn)換指令
類型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類型進(jìn)行相互轉(zhuǎn)換。這些轉(zhuǎn)換操作一般用于實現(xiàn)用戶代碼中的顯式類型轉(zhuǎn)換操作,
或者用來解決字節(jié)碼指令集不完備的問題。
因為數(shù)據(jù)類型相關(guān)指令無法與數(shù)據(jù)類型一一對應(yīng)的問題,比如byte short char boolean使用int,?? 所以必須要轉(zhuǎn)換。
分為寬化 和 窄化含義如字面含義,存儲長度的變寬或者變窄寬化也就是常說的安全轉(zhuǎn)換,不會因為超過目標(biāo)類型最大值丟失信息。窄化則意味著很可能會丟失信息。
寬化指令和窄化指令的形式為? 操作類型 2 (to)? 目標(biāo)類型? 比如 i2l int 轉(zhuǎn)換為long
寬化指令
- int類型到long、float或者double類型 (i2l、i2f、i2d)
- long類型到float、double類型(l2f 、l2d)
- float類型到double類型(f2d)
窄化指令
- int類型到byte short char類型(i2b 、i2s 、i2c)
- long類型到int類型(l2i)
- float類型到int或者long類型(f2i 、f2l)
- double類型到int long 或者float類型(d2i 、d2l 、d2f)
四、對象的創(chuàng)建與訪問
實例和數(shù)組都是對象但是Java虛擬機(jī)對類實例和數(shù)組的創(chuàng)建使用了不同的字節(jié)碼指令
五、操作數(shù)棧管理指令
六、控制轉(zhuǎn)移指令
七、方法調(diào)用與返回指令
八、異常處理指令
九、同步指令
?
?
class文件加深了解參考:https://www.bbsmax.com/A/xl56rAEo5r/
?
總結(jié)
- 上一篇: JVM-垃圾收集器与内存分配策略
- 下一篇: JVM--类加载机制