JVM学习-Class文件结构
文章原文:https://gaoyubo.cn/blogs/844dc0e7.html
一、Class類文件的結(jié)構(gòu)
任何一個Class文件都對應(yīng)著唯一的一個類或接口的定義信息。
但是反過來說,類或接口并不一定都得定義在文件里(譬如類或接口也可以動態(tài)生成,直接送入類加載器中)。
Class 文件是一組以 8 位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在 Class 文件中,中間沒有任何分隔符。
Java 虛擬機(jī)規(guī)范規(guī)定 Class 文件采用一種類似 C 語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲數(shù)據(jù),這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號數(shù)和表。
- 無符號數(shù): 無符號數(shù)屬于基本數(shù)據(jù)類型,以 u1、u2、u4、u8 分別代表 1 個字節(jié)、2 個字節(jié)、4 個字節(jié)和 8 個字節(jié)的無符號數(shù),可以用它來描述數(shù)字、索引引用、數(shù)量值或 utf-8 編碼的字符串值。
-
表: 表是由多個無符號數(shù)或其他表為數(shù)據(jù)項構(gòu)成的復(fù)合數(shù)據(jù)類型,名稱上都以
_info結(jié)尾。
整個Class文件本質(zhì)上也可以視作是一張表,這張表由數(shù)據(jù)項按嚴(yán)格順序排列構(gòu)成
| 英文名稱 | 中文名稱 | 類型 | 數(shù)量 |
|---|---|---|---|
| magic | 魔數(shù) | u4 | 1 |
| minor_version | 次版本號 | u2 | 1 |
| major_version | 主版本號 | u2 | 1 |
| constant_pool_count | 常量池計數(shù) | u2 | 1 |
| constant_pool | 常量池 | cp_info | constant_pool_count - 1 |
| access_flags | 訪問標(biāo)志 | u2 | 1 |
| this_class | 類索引 | u2 | 1 |
| super_class | 父類索引 | u2 | 1 |
| interfaces_count | 接口計數(shù) | u2 | 1 |
| interfaces | 接口索引集合 | u2 | interfaces_count |
| fields_count | 字段計數(shù) | u2 | 1 |
| fields | 字段表集合 | field_info | fields_count |
| methods_count | 方法計數(shù) | u2 | 1 |
| methods | 方法表集合 | method_info | methods_count |
| attributes_count | 屬性計數(shù) | u2 | 1 |
| attributes | 屬性集合 | attribute_info | attributes_count |
其中,cp_info 、field_info、method_info 和 attribute_info 是更具體的結(jié)構(gòu),包含了常量池項、字段信息、方法信息和屬性信息的詳細(xì)描述。
無論是無符號數(shù)還是表,當(dāng)需要描述同一類型但數(shù)量不定的多個數(shù)據(jù)時,經(jīng)常會使用一個前置的容量計數(shù)器加若干個連續(xù)的數(shù)據(jù)項的形式,這時候稱這一系列連續(xù)的某一類型的數(shù)據(jù)為某一類型的“集
合”。
示例
package algorithmAnalysis;
public class JVMTest {
private int m;
public int inc(){
return m+1;
}
public static void main(String[] args) {
System.out.println("gaoyubo");
}
}
1.1魔數(shù)與版本號
Class 文件的頭 8 個字節(jié)是魔數(shù)和版本號,其中頭 4 個字節(jié)是魔數(shù),也就是 0xCAFEBABE,它可以用來確定這個文件是否為一個能被虛擬機(jī)接受的 Class 文件(這通過擴(kuò)展名來識別文件類型要安全,畢竟擴(kuò)展名是可以隨便修改的)。
后 4 個字節(jié)則是當(dāng)前 Class 文件的版本號,其中第 5、6 個字節(jié)是次版本號,第 7、8 個字節(jié)是主版本號。
1.2常量池
從第 9 個字節(jié)開始,就是常量池的入口,常量池是 Class 文件中:
- 與其他項目關(guān)聯(lián)最多的的數(shù)據(jù)類型;
- 占用 Class 文件空間最大的數(shù)據(jù)項目;
- Class 文件中第一個出現(xiàn)的表類型數(shù)據(jù)項目。
常量池的前兩個字節(jié),即第 9、10 個字節(jié),存放著一個 u2 類型的數(shù)據(jù),用于表示常量池中的常量數(shù)量 cpc(constant_pool_count)。
這個計數(shù)值有一個特殊之處,即它是從 1 開始而不是從 0 開始的。
舉例而言,如果cpc = 22,那么說明常量池中包含 21 個常量,它們的索引值為 1 到 21。
第 0 項常量被保留為空,以便在某些情況下表示“不引用任何常量池項目”,此時將索引值設(shè)為 0 即可。
常量池中記錄主要包括以下兩大類常量:
-
字面量: 接近于 Java 語言層面的常量概念
- 文本字符串
- 聲明為 final 的常量值
-
符號引用:以一組符號來描述所引用的目標(biāo)
- 被模塊導(dǎo)出或開放的包(Package)
- 類和接口的全限定名(Fully Qualified Name)
- 字段的名稱和描述符(Descriptor)
- 方法的名稱和描述符
- 方法句柄和方法類型(Method Handle、Method Type、Invoke Dynamic)
- 動態(tài)調(diào)用點動態(tài)常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
| 常量類型 | 標(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 | 方法句柄 |
| CONSTANT_MethodType_info | 16 | 方法類型 |
| CONSTANT_Dynamic_info | 17 | 動態(tài)計數(shù)常量 |
| CONSTANT_InvokeDynamic_info | 18 | 動態(tài)方法調(diào)用點 |
| CONSTANT_Module_info | 19 | 模塊信息 |
| CONSTANT_Package_info | 20 | 包信息 |
CONSTANT_Class_info
... [ tag=7 ] [ name_index ] ...
... [ 1位 ] [ 2位 ] ...
- tag 是標(biāo)志位,用來區(qū)分常量類型的,tag = 7 就表示接下來的這個表是一個 CONSTANT_Class_info。
- name_index 是一個索引值,指向常量池中的一個 CONSTANT_Utf8_info 類型的常量所在的索引值,CONSTANT_Utf8_info 類型常量一般被用來描述類的全限定名、方法名和字段名。它的存儲結(jié)構(gòu)如下:
... [ tag=1 ] [ 當(dāng)前常量的長度 len ] [ 常量的符號引用的字符串值 ] ...
... [ 1位 ] [ 2位 ] [ len位 ] ...
CONSTANT_Fieldref_info
| 類型 | 名稱 | 數(shù)量 |
|---|---|---|
| ul | tag | 1 |
| u2 | class_index | 1 |
| u2 | name_and_type_index | 1 |
- tag: 表示標(biāo)簽,值為CONSTANT_Fieldref(9)。
- class_index: 是一個指向CONSTANT_Class_info表的索引,該表中存儲了字段所屬的類或接口。
- name_and_type_index: 是一個指向CONSTANT_NameAndType_info表的索引,該表中存儲了字段的名稱和描述符。
CONSTANT_Method ref_into
以下是對固定長度的CONSTANT_Methodref_info表使用符號引用來表示類中聲明的方法(不包括接口中的方法)進(jìn)行優(yōu)化和潤色后的描述:固定長度的CONSTANT_Methodref_info表使用符號引用來表示類中聲明的方法(不包括接口中的方法)。
| 類型 | 名稱 | 數(shù)量 |
|---|---|---|
| ul | tag | 1 |
| u2 | class_index | 1 |
| u2 | name_and_type_index | 1 |
-
tag(標(biāo)簽):tag項的值為CONSTANT_Methodref (10)。
-
class_index(類索引):class_index項給出了聲明了被引用方法的類的CONSTANT_Class_info表的索引。class_index所指定的CONSTANT_Class_info表必須表示一個類,而不能是接口。指向接口中聲明的方法的符號引用應(yīng)使用CONSTANT_InterfaceMethodref表。
-
name_and_type_index(名稱和類型索引):name_and_type_index提供了CONSTANT_NameAndType_info表的索引,該表提供了方法的簡單名稱和描述符。如果方法的簡單名稱以"<"(\u003c)符號開頭,則該方法必須是一個實例化方法。它的簡單名稱應(yīng)為"",并且返回類型必須為void。否則,該方法應(yīng)該是一個常規(guī)方法。
CONSTANT_String_info
尚定長度的CONSTANT_String_info表用于存儲文字字符串值,這些值可以表示為java.lang.String類的實例。該表僅存儲文字字符串值,不存儲符號引用。
| 類型 | 名稱 | 數(shù)量 |
|---|---|---|
| ul | tag | 1 |
| u2 | string_index | 1 |
- tag: 表示標(biāo)簽,值為CONSTANT_String(8)。
- string_index: 是一個指向CONSTANT_Utf8_info表的索引,該表中存儲了實際的字符串值。通過使用這樣的表形式,可以方便地存儲和引用字符串值,保證了程序的靈活性和可讀性。
如果全部介紹,篇幅太長,這里使用IDEA的jclasslib插件,查看效果如下:
常量表中常量項定義如下:
1.3訪問標(biāo)志
在常量池結(jié)束之后,緊接著的2個字節(jié)代表訪問標(biāo)志(access_flags),這個標(biāo)志用于識別一些類或者接口層次的訪問信息,包括:
這個Class是類還是接口?
- 接口:
- 是否定義為public類型;
- 是否定義為abstract類型;
- 類:
- 是否被聲明為final;
以下為訪問標(biāo)志定義:
| 標(biāo)志名稱 | 標(biāo)志值 | 含義 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 類或接口是公共的 |
| ACC_FINAL | 0x0010 | 類不能被繼承;方法不能被重寫 |
| ACC_SUPER | 0x0020 | 當(dāng)用 invokespecial 指令調(diào)用超類構(gòu)造方法時,要求對該方法的調(diào)用使用 super 關(guān)鍵字 |
| ACC_INTERFACE | 0x0200 | 標(biāo)記接口 |
| ACC_ABSTRACT | 0x0400 | 類沒有實現(xiàn)所有的接口方法 |
| ACC_SYNTHETIC | 0x1000 | 標(biāo)記為由編譯器生成的類或方法 |
| ACC_ANNOTATION | 0x2000 | 標(biāo)記為注解類型 |
| ACC_ENUM | 0x4000 | 標(biāo)記為枚舉類型 |
| ACC_MODULE | 0x8000 | 標(biāo)記為模塊 |
訪問標(biāo)識通常是通過按位或運(yùn)算符(|)進(jìn)行計算的。每個訪問標(biāo)識都對應(yīng)一個二進(jìn)制位,通過將需要的標(biāo)識的二進(jìn)制位進(jìn)行按位或運(yùn)算,可以組合多個標(biāo)識。
上文的JVMTest.java:它的訪問標(biāo)識應(yīng)該是
ACC_PUBLIC和ACC_SUPER。以下是分析:
ACC_PUBLIC(0x0001): 這個標(biāo)志表示類是公共的,可以從其他包訪問。ACC_SUPER(0x0020): 在 Java 5 之前,這個標(biāo)志是為了向后兼容,當(dāng)使用invokespecial指令調(diào)用超類構(gòu)造方法時,要求對該方法的調(diào)用使用super關(guān)鍵字。因此,
JVMTest類的訪問標(biāo)識應(yīng)該是ACC_PUBLIC | ACC_SUPER,即 0x0021。
1.4類索引、父類索引與接口索引
類索引(this_class)和父類索引(super_class)
-
類型:
-
this_class和super_class都是u2類型的數(shù)據(jù)。
-
-
作用:
-
this_class用于確定這個類的全限定名。 -
super_class用于確定這個類的父類的全限定名。
-
-
繼承關(guān)系:
- 由于 Java 不允許多重繼承,父類索引只有一個。
- 除了
java.lang.Object之外,所有 Java 類都有父類。 - 所以,除了
java.lang.Object外,所有 Java 類的父類索引都不為 0。
-
索引值:
- 類索引(
this_class)和父類索引(super_class)分別用兩個u2類型的索引值表示。 - 這兩個索引值分別指向一個類型為
CONSTANT_Class_info的類描述符常量。
- 類索引(
-
全限定名查找:
- 通過
CONSTANT_Class_info類型的常量中的索引值,可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串。
- 通過
接口索引集合(interfaces)
-
類型:
-
interfaces是一組u2類型的數(shù)據(jù)的集合。
-
-
作用:
- 用于描述這個類實現(xiàn)了哪些接口。
-
排列順序:
- 接口索引集合中的接口將按
implements關(guān)鍵字后的接口順序從左到右排列。
- 接口索引集合中的接口將按
-
注意事項:
- 如果這個 Class 文件表示的是一個接口,則應(yīng)當(dāng)使用
extends關(guān)鍵字。
- 如果這個 Class 文件表示的是一個接口,則應(yīng)當(dāng)使用
通過這三項數(shù)據(jù),可以建立起類的繼承關(guān)系和接口實現(xiàn)關(guān)系,確定類的層次結(jié)構(gòu)和實現(xiàn)的接口,如下為全限定名索引查找過程。
class文件中示例
訪問標(biāo)志后面緊跟類索引、父類索引、接口索引,JVMTest.class中表示如下,這里類索引u2值為0x0005,父類索引u2值為0x0006:
使用jclasslib查看u2值對應(yīng)常量如下,可以看出JVMTest類的父類為Object類:
1.5字段表集合
-
描述:
-
field_info用于描述接口或類中聲明的字段(變量)。 - 字段包括類級變量和實例級變量,但不包括在方法內(nèi)部聲明的局部變量。
-
-
字段信息包含的修飾符:
-
作用域修飾符: 可以是
public、private、protected。 -
變量類型修飾符: 區(qū)分實例變量和類變量,使用
static修飾符。 -
可變性修飾符: 使用
final修飾符。 -
并發(fā)可見性修飾符: 使用
volatile修飾符,表示是否強(qiáng)制從主內(nèi)存讀寫。 -
序列化修飾符: 使用
transient修飾符,表示是否可被序列化。
-
作用域修飾符: 可以是
-
字段數(shù)據(jù)類型:
- 包括基本類型、對象和數(shù)組等。
- 數(shù)據(jù)類型不固定,通過引用常量池中的常量來描述。
-
字段名稱:
- 字段名稱不固定,通過引用常量池中的常量來描述。
-
修飾符的表示:
- 修飾符都是布爾值,要么存在某個修飾符,要么不存在。
- 使用標(biāo)志位來表示修飾符的存在與否,以便緊湊地表示多個修飾符。
通過 field_info,可以詳細(xì)描述字段的各種屬性和特征,為 Java 類或接口的字段提供了靈活而精確的定義。
因此字段表結(jié)構(gòu)定義如下:
| 名稱 | 類型 | 描述 | 數(shù)量 |
|---|---|---|---|
| access_flags | u2 | 訪問標(biāo)志 | 1 |
| name_index | u2 | 字段名索引 | 1 |
| descriptor_index | u2 | 描述符索引 | 1 |
| attributes_count | u2 | 屬性計數(shù) | 1 |
| attributes | attribute_info | 屬性集合 | attributes_count |
字段表訪問標(biāo)志(access_flags)
其中,access_flags字段訪問標(biāo)志定義如下:
| 名稱 | 標(biāo)志值 | 描述 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 公共訪問標(biāo)志 |
| ACC_PRIVATE | 0x0002 | 私有訪問標(biāo)志 |
| ACC_PROTECTED | 0x0004 | 受保護(hù)訪問標(biāo)志 |
| ACC_STATIC | 0x0008 | 靜態(tài)字段標(biāo)志 |
| ACC_FINAL | 0x0010 | 常量字段標(biāo)志 |
| ACC_VOLATILE | 0x0040 | 可變字段標(biāo)志(并發(fā)可見性) |
| ACC_TRANSIENT | 0x0080 | 短暫字段標(biāo)志(不可序列化) |
| ACC_SYNTHETIC | 0x1000 | 由編譯器自動產(chǎn)生的標(biāo)志 |
| ACC_ENUM | 0x4000 | 枚舉類型字段標(biāo)志 |
簡單描述和描述符(name_index和descriptor_index)
跟隨access_flags標(biāo)志的是兩項索引值:name_index和descriptor_index。
- 這兩個索引值緊隨
access_flags標(biāo)志之后,分別引用常量池中的項。 -
name_index代表字段的簡單名稱,指向常量池中的字符串項。 -
descriptor_index代表字段和方法的描述符,同樣指向常量池中的字符串項。
全限定名: 類似于
org/fenixsoft/clazz/TestClass,是類的完整名稱,將包名中的.替換為/。為了在使用時避免混淆,通常在最后加入一個分號;表示全限定名結(jié)束。簡單名稱: 指沒有類型和參數(shù)修飾的方法或字段名稱。例如,
inc和m是inc()方法和m字段的簡單名稱。描述符:
- 描述符用于描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型和順序)以及返回值。
- 基本數(shù)據(jù)類型(
byte、char、double、float、int、long、short、boolean)以及代表無返回值的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ù)組類型,可以嵌套,java.lang.String[][]類型的二維數(shù)組將被記錄成[[Ljava/lang/String一個整型數(shù)組 int[]將被記錄成[I
|
方法描述符按照先參數(shù)列表、后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組小括號 () 之內(nèi)。
-
無參數(shù)、無返回值的方法(如
void inc()):- 描述符為
()V。
- 描述符為
-
有返回值的方法(如
java.lang.String toString()):- 描述符為
()Ljava/lang/String;。 - 參數(shù)列表為空,返回值為對象類型(
Ljava/lang/String;)。
- 描述符為
-
有多個參數(shù)和返回值的方法(如
int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)):- 描述符為
([CII[CIII)I。 - 參數(shù)列表:
-
([C:char 數(shù)組類型 -
II:兩個 int 類型 -
[C:另一個 char 數(shù)組類型 -
III:三個 int 類型
-
- 返回值:
I表示 int 類型。
- 描述符為
屬性表
字段表中的固定數(shù)據(jù)項一直到 descriptor_index 為止,而在 descriptor_index 之后,跟隨著一個屬性表集合。這個屬性表集合用于存儲一些額外的信息,允許字段表附加描述零至多項的額外信息。
-
屬性表計數(shù)器:
- 用于記錄附加到字段上的屬性個數(shù)。
- 計數(shù)器的值決定了接下來有多少個屬性項。
-
屬性表中可能的額外信息:
- ConstantValue 屬性:
- 如果字段被聲明為
final static int m = 123;,則可能存在一項名稱為ConstantValue的屬性。 - 這個屬性的值指向常量 123。
- 如果字段被聲明為
- ConstantValue 屬性:
-
其他屬性項:
- 根據(jù)字段的具體聲明,可能存在其他類型的屬性,如訪問控制等。
通過屬性表集合,字段表可以攜帶額外的信息,例如常量值、訪問控制等,以滿足不同字段的需求。在本例中,由于字段 m 的聲明為 final static int m = 123;,因此可能包含 ConstantValue 屬性,指向常量 123。
字段表集合的特性
-
不包含從父類或父接口中繼承的字段:
- 字段表集合中不會列出從父類或者父接口中繼承而來的字段。
- 繼承的字段在子類的字段表中不會重復(fù)出現(xiàn),因為已經(jīng)在父類的字段表中定義。
-
可能包含編譯器生成的字段:
- 在某些情況下,編譯器會自動添加一些字段,例如在內(nèi)部類中為了保持對外部類的訪問性,可能會自動添加指向外部類實例的字段。
-
字段重名的合法性:
- 在 Java 語言中,字段是無法重載的,即兩個字段的數(shù)據(jù)類型、修飾符不管是否相同,都必須使用不同的名稱。
- 但在 Class 文件格式中,只要兩個字段的描述符不是完全相同,字段重名是合法的。描述符不同即使字段名稱相同也是合法的。
class文件中示例
在class文件中,表示如下,按照順序分別是fields_count,access_flags,name_index,descriptor_index:
0x0001:說明這個類只有一個字段表數(shù)據(jù)
0x0002:代表private修飾符的ACC_PRIVATE 標(biāo)志位為真(ACC_PRIVATE標(biāo)志的值為0x0002)
0x0008:字面量為m,在常量池中對應(yīng)內(nèi)容如下圖
0x0009:字面量I,在常量池中對應(yīng)內(nèi)容如下圖
與類訪問標(biāo)志相同,字段訪問標(biāo)志計算字段訪問標(biāo)志的值也是通過按位或(
|)操作將各個標(biāo)志的值組合而成的。例如,如果一個字段是
public和static的,那么其訪問標(biāo)志的值為ACC_PUBLIC | ACC_STATIC如果有兩個字段,那么這個順序就會重復(fù)兩次,依次表示兩個字段的描述信息。
1.6方法表集合
Class文件存儲 格式中對方法的描述與對字段的描述采用了幾乎完全一致的方式,方法表的結(jié)構(gòu)如同字段表一樣。
依次包括訪問標(biāo)志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項
因此方法表表結(jié)構(gòu)定義如下:
| 名稱 | 類型 | 描述 | 數(shù)量 |
|---|---|---|---|
| access_flags | u2 | 訪問標(biāo)志 | 1 |
| name_index | u2 | 方法名索引 | 1 |
| descriptor_index | u2 | 描述符索引 | 1 |
| attributes_count | u2 | 屬性計數(shù) | 1 |
| attributes | attribute_info | 屬性集合 | attributes_count |
方法的定義可以通過訪問標(biāo)志、名稱索引、描述符索引來表達(dá)清楚,但方法內(nèi)部的Java代碼去哪里了?
方法內(nèi)的Java代碼在經(jīng)過Javac編譯器編譯成字節(jié)碼指令后,實際上存放在方法屬性表集合中的一個名為“Code”的屬性里面。屬性表作為Class文件格式中最具擴(kuò)展性的一種數(shù)據(jù)項目,將在后續(xù)介紹。
方法表的訪問標(biāo)志
方法表的訪問標(biāo)志中不包含 ACC_VOLATILE 和 ACC_TRANSIENT 標(biāo)志,因為 volatile 和 transient 關(guān)鍵字不能修飾方法。
相反,方法表的訪問標(biāo)志中增加了以下標(biāo)志,因為這些關(guān)鍵字可以修飾方法:
-
ACC_SYNCHRONIZED:用于修飾同步方法,表示該方法是同步方法。 -
ACC_NATIVE:表示該方法用其他語言(如 C)實現(xiàn),由本地方法庫提供。 -
ACC_STRICTFP:表示該方法遵循 IEEE 754 浮點運(yùn)算規(guī)范。 -
ACC_ABSTRACT:表示該方法是抽象方法,沒有具體的實現(xiàn)。
以下是方法表的訪問標(biāo)志及其取值:
| 標(biāo)志名稱 | 標(biāo)志值 | 描述 |
|---|---|---|
| ACC_PUBLIC | 0x0001 | 公共訪問標(biāo)志 |
| ACC_PRIVATE | 0x0002 | 私有訪問標(biāo)志 |
| ACC_PROTECTED | 0x0004 | 受保護(hù)訪問標(biāo)志 |
| ACC_STATIC | 0x0008 | 靜態(tài)方法標(biāo)志 |
| ACC_FINAL | 0x0010 | 常量方法標(biāo)志 |
| ACC_SYNCHRONIZED | 0x0020 | 同步方法標(biāo)志 |
| ACC_BRIDGE | 0x0040 | 橋接方法標(biāo)志 |
| ACC_VARARGS | 0x0080 | 可變參數(shù)方法標(biāo)志 |
| ACC_NATIVE | 0x0100 | 本地方法標(biāo)志 |
| ACC_ABSTRACT | 0x0400 | 抽象方法標(biāo)志 |
| ACC_STRICTFP | 0x0800 | 嚴(yán)格浮點標(biāo)志 |
| ACC_SYNTHETIC | 0x1000 | 由編譯器自動生成的標(biāo)志 |
class文件中示例
按照順序分別為:method_count,access_flags,name_index,descriptor_index,attributes_count,attribute_name_index
0x0003(method_count):說明這個類有三個方法,編譯器自動添加了<init>方法,即實例構(gòu)造器,如下:
0x0001(access_flags):只有ACC_PUBLIC標(biāo)志為真
0x000A(name_index):字面量索引位10:字面量為<init>
0x000B(descriptor_index):字面量索引位11,字面量()V,代表void返回類型,參數(shù)列表為空
0x0001(attributes_count):表示此方法的屬性表集合有1項屬性
0x000C(attribute_name_index):屬性名稱的索引值為0x000C,對應(yīng)常量為“Code”
字段表集合相對應(yīng)地,如果父類方法在子類中沒有被重寫(Override),方法表集合中就不會出 現(xiàn)來自父類的方法信息。
但同樣地,有可能會出現(xiàn)由編譯器自動添加的方法,最常見的便是類構(gòu)造器<clinit>()方法和實例構(gòu)造器<init>()方法
在Java語言中,要重載一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的
特征簽名(Java代碼的方法特征簽名只包括方法名稱、參數(shù)順序及參數(shù)類型,而字節(jié)碼的特征簽名還包括方法返回值以及受查異常表)。
由于返回值不包含在特征簽名中,因此無法僅僅通過返回值的不同來對一個已有方法進(jìn)行重載,如下圖。
然而,在Class文件格式中,特征簽名的范圍明顯更大。只要兩個方法的描述符不完全相同,它們就可以在同一個Class文件中合法共存。具體來說,如果兩個方法具有相同的名稱和特征簽名,但返回值不同,它們?nèi)匀豢梢栽谕粋€Class文件中存在。
1.7屬性表集合
屬性表(attribute_info)在前面的講解之中已經(jīng)出現(xiàn)過數(shù)次,Class文件、字段表、方法表都可以 攜帶自己的屬性表集合,以描述某些場景專有的信息。
在《Java虛擬機(jī)規(guī)范》的Java SE 12版本中,預(yù)定義屬性已經(jīng)增加到29項,如下:
對于每一個屬性,它的名稱都要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示, 而屬性值的結(jié)構(gòu)則是完全自定義的,只需要通過一個u4的長度屬性去說明屬性值所占用的位數(shù)即可。一個符合規(guī)則的屬性表應(yīng)該滿足下表結(jié)構(gòu)。
| 名稱 | 類型 | 數(shù)量 |
|---|---|---|
| attribute_name_index | u2 | 1 |
| attribute_length | u4 | 1 |
| info | u1 | attribute_count |
Code屬性
在Java程序中,方法體內(nèi)的代碼在經(jīng)過Javac編譯器處理之后,最終被轉(zhuǎn)化為字節(jié)碼指令,并存儲在方法表的屬性集合中的Code屬性內(nèi)。需要注意的是,并非所有的方法表都必須包含Code屬性。例如,在接口或抽象類中的方法就不存在Code屬性。
| 屬性名稱 | 類型 | 描述 | 數(shù)量 |
|---|---|---|---|
| attribute_name_index | u2 | 指向UTF-8常量的索引,表示屬性名稱(Code) | 1 |
| max_stack | u2 | 操作數(shù)棧的最大深度 | 1 |
| max_locals | u2 | 局部變量表的最大容量 | 1 |
| code_length | u4 | 字節(jié)碼指令的長度 | 1 |
| code | u1[code_length] | 存儲實際字節(jié)碼指令的數(shù)組 | code_length |
| exception_table_length | u2 | 異常處理表的長度 | 1 |
| exception_table | exception_info | 異常處理表 | 0或多 |
| attributes_count | u2 | Code屬性的屬性數(shù)量 | 1 |
| attributes | attribute_info[attributes_count] | Code屬性的屬性集合 | 0或多 |
- max_stack: 操作數(shù)棧的最大深度,在方法執(zhí)行的任意時刻,操作數(shù)棧都不會超過這個深度。
- max_locals: 局部變量表所需的存儲空間,以變量槽為單位,變量槽是虛擬機(jī)為局部變量分配內(nèi)存的最小單位。
- code_length: 字節(jié)碼指令的長度,限制為不超過65535字節(jié)。
- code: 存儲實際字節(jié)碼指令的一系列字節(jié)流。
- exception_table_length: 異常處理表的長度,記錄方法中的異常處理信息。
- exception_table: 異常處理表,包括起始字節(jié)碼指令位置、結(jié)束字節(jié)碼指令位置、異常處理程序入口位置和捕獲異常的類索引。
- attributes_count: Code屬性的屬性數(shù)量,用于存儲額外的屬性信息。
- attributes: Code屬性的屬性集合,可能包含一些額外的信息,如調(diào)試信息等。
class文件中示例
屬性表的attribute_name_index后的00 00 00 2F表示屬性值的長度。在這里,00 00 00 2F表示長度為47個字節(jié)。它告訴虛擬機(jī)在讀取屬性值時要讀取47個字節(jié)的內(nèi)容。如果前面的0x000C的字面量Code虛擬機(jī)不認(rèn)識,那么就可以跳過這些長度。
《Java虛擬機(jī)規(guī)范》允許只要不與已有屬性名重復(fù),任何人實現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱约憾x的屬性信息,Java虛擬機(jī)運(yùn)行時會忽略掉它不認(rèn)識的屬性。
按順序分別為:max_stack,max_locals,code_length,code
0x0001: 操作數(shù)棧的最大深度為1
0x0001: 本地變量表容量為1
0x00000005: 字節(jié)碼區(qū)域 所占空間的長度為0x0005。虛擬機(jī)讀取到字節(jié)碼區(qū)域的長度后,按照順序依次讀入緊隨的5個字節(jié),并
根據(jù)字節(jié)碼指令表翻譯出所對應(yīng)的字節(jié)碼指令
翻譯“2A B7000A B1”的過程為:
- 讀入
2A,查表得到aload_0指令,作用是將第 0 個變量槽中的reference類型的本地變量推送到操作數(shù)棧頂。 - 讀入
B7,查表得到invokespecial指令,該指令以棧頂?shù)?reference類型的數(shù)據(jù)所指向的對象作為方法接收者,調(diào)用此對象的實例構(gòu)造器方法、private方法或者它的父類的方法。該方法有一個u2類型的參數(shù),指向常量池中的一個CONSTANT_Methodref_info類型常量,即此方法的符號引用。 - 讀入
000A,這是invokespecial指令的參數(shù),代表一個符號引用。查常量池得到0x000A對應(yīng)的常量,表示實例構(gòu)造器<init>()方法的符號引用。 - 讀入
B1,查表得到return指令,含義是從方法返回,并且返回值為void。執(zhí)行這條指令后,當(dāng)前方法正常結(jié)束。
這里查的表是 Java 虛擬機(jī)規(guī)范中定義的字節(jié)碼指令表。字節(jié)碼指令表包含了每個操作碼(opcode)對應(yīng)的具體指令和操作。
部分其他指令如下:
指令 助記符 描述 0x03 iconst_2 將整數(shù)常量值 2 推送到操作數(shù)棧頂 0x10 bipush 將一個字節(jié)推送到棧頂,作為整數(shù)使用 0x60 iadd 將棧頂兩個整數(shù)相加 0x2D fsub 將棧頂兩個浮點數(shù)相減 0xC7 ifnonnull 如果引用不為 null,則跳轉(zhuǎn)
異常表
在字節(jié)碼指令之后的是這個方法的顯式異常處理表(下文簡稱“異常表”)集合,異常表對于Code 屬性來說并不是必須存在的。
異常表的格式如下:
| 字段名 | 數(shù)據(jù)類型 | 描述 |
|---|---|---|
| start_pc | u2 | 起始字節(jié)碼行號 |
| end_pc | u2 | 結(jié)束字節(jié)碼行號(不含) |
| handler_pc | u2 | 異常處理代碼的字節(jié)碼行號 |
| catch_type | u2 | 指向一個CONSTANT_Class_info型常量的索引,表示捕獲的異常類型。為0時表示捕獲所有異常。 |
演示:
public int inc() {
int x;
try {
x = 1;
return x;
} catch (Exception e) {
x = 2;
return x;
} finally {
x = 3;
}
}
編譯后的字節(jié)碼和異常表:
public int inc();
Code:
Stack=1, Locals=5, Args_size=1
0: iconst_1 // 將整數(shù)1推送到棧頂,try塊中的x=1
1: istore_1 // 將棧頂?shù)闹荡鎯Φ奖镜刈兞勘淼淖兞坎?中
2: iload_1 // 將本地變量表中的變量槽1的值推送到棧頂
3: istore 4 // 將棧頂?shù)闹荡鎯Φ奖镜刈兞勘淼淖兞坎?中
5: iconst_3 // 將整數(shù)3推送到棧頂,finally塊中的x=3
6: istore_1 // 將棧頂?shù)闹荡鎯Φ奖镜刈兞勘淼淖兞坎?中
7: iload 4 // 將本地變量表中的變量槽4的值推送到棧頂
9: ireturn // 從方法返回,返回值為棧頂?shù)闹?
10: astore_2 // 將棧頂?shù)漠惓ο蟠鎯Φ奖镜刈兞勘淼淖兞坎?中
11: iconst_2 // 將整數(shù)2推送到棧頂,catch塊中的x=2
12: istore_1 // 將棧頂?shù)闹荡鎯Φ奖镜刈兞勘淼淖兞坎?中
13: iload_1 // 將本地變量表中的變量槽1的值推送到棧頂
14: istore 4 // 將棧頂?shù)闹荡鎯Φ奖镜刈兞勘淼淖兞坎?中
16: iconst_3 // 將整數(shù)3推送到棧頂,finally塊中的x=3
17: istore_1 // 將棧頂?shù)闹荡鎯Φ奖镜刈兞勘淼淖兞坎?中
18: iload 4 // 將本地變量表中的變量槽4的值推送到棧頂
20: ireturn // 從方法返回,返回值為棧頂?shù)闹?
21: astore_3 // 將棧頂?shù)漠惓ο蟠鎯Φ奖镜刈兞勘淼淖兞坎?中
22: iconst_3 // 將整數(shù)3推送到棧頂,finally塊中的x=3
23: istore_1 // 將棧頂?shù)闹荡鎯Φ奖镜刈兞勘淼淖兞坎?中
24: aload_3 // 將本地變量表中的變量槽3的值(異常對象)推送到棧頂
25: athrow // 拋出棧頂?shù)漠惓?
Exception table:
from to target type
0 0 10 Class java/lang/Exception
5 5 16 any
10 21 21 Class java/lang/Exception
在這段字節(jié)碼中,前五行主要是try塊的內(nèi)容。首先,整數(shù)1被賦給變量x,然后通過istore_1指令將x的值保存在第一個本地變量槽(slot)中。接下來,將3推送到操作數(shù)棧,再通過istore指令將其存儲在第四個本地變量槽中,這個槽被稱為returnValue。
接下來的iload_1指令將第一個本地變量槽中的x值加載到操作數(shù)棧頂,然后通過ireturn指令返回這個值。因此,如果try塊中沒有異常,方法將返回1。
在異常情況下,程序?qū)⑻D(zhuǎn)到第10行(catch塊)。異常處理塊首先將2賦給變量x,然后通過istore_1指令將x的值保存在第一個本地變量槽中。接著,將之前保存在returnValue中的值(即1)加載到操作數(shù)棧頂,然后通過ireturn指令返回這個值。因此,如果發(fā)生異常,方法將返回2。
最后,無論是否發(fā)生異常,程序都會執(zhí)行finally塊(第21行開始)。在finally塊中,將3賦給變量x,并使用athrow指令拋出之前發(fā)生的異常。雖然這里沒有具體的異常類型,但finally塊的主要目的是在方法返回前執(zhí)行清理工作。
Exceptions屬性
這里的Exceptions屬性是在方法表中與Code屬性平級的一項屬性,不要與前面剛剛講解完的異常表產(chǎn)生混淆。
Exceptions屬性的作用是列舉出方法中可能拋出的受查異常(Checked Excepitons),也就是方法描述時在throws關(guān)鍵字后面列舉的異常。
| 字段名 | 類型 | 描述 |
|---|---|---|
| attribute_name_index | u2 | 指向常量池中CONSTANT_Utf8_info類型的異常表屬性名稱的索引 |
| attribute_length | u4 | 屬性值的長度,不包括attribute_name_index和attribute_length自身的長度 |
| number_of_exceptions | u2 | 異常表中的異常個數(shù) |
| exception_index_table | u2 數(shù)組 | 每個元素都是指向常量池中CONSTANT_Class_info類型的索引,表示受檢異常的類型 |
LineNumberTable 屬性
LineNumberTable屬性用于描述Java源碼行號與字節(jié)碼行號(字節(jié)碼的偏移量)之間的對應(yīng)關(guān)系。雖然它不是運(yùn)行時必需的屬性,但默認(rèn)會生成到Class文件中。通過使用Javac中的-g:none或-g:lines選項,可以選擇是否生成這項信息。如果選擇不生成LineNumberTable屬性,對程序運(yùn)行的主要影響之一是在拋出異常時,堆棧跟蹤中將不會顯示出錯的行號。此外,調(diào)試程序時也無法按照源碼行來設(shè)置斷點。
在調(diào)試和排查問題時,LineNumberTable屬性是非常有用的,因為它建立了Java源代碼和編譯后的字節(jié)碼之間的映射。
| 字段名 | 類型 | 描述 |
|---|---|---|
| attribute_name_index | u2 | 指向常量池中CONSTANT_Utf8_info類型的屬性名稱 "LineNumberTable" 的索引 |
| attribute_length | u4 | 屬性值的長度,不包括 attribute_name_index 和 attribute_length 自身的長度 |
| line_number_table | 表 | 包含多個行號項的表,每個行號項包括 start_pc 和 line_number 字段,表示字節(jié)碼行號和源代碼行號的映射關(guān)系 |
LocalVariableTable 屬性
LocalVariableTable屬性用于描述棧幀中局部變量表的變量與Java源碼中定義的變量之間的關(guān)系。雖然它不是運(yùn)行時必需的屬性,但默認(rèn)會生成到Class文件中。可以使用Javac中的-g:none或-g:vars選項來選擇是否生成這項信息。如果沒有生成這項屬性,最大的影響之一是當(dāng)其他人引用這個方法時,所有的參數(shù)名稱都將會丟失。例如,IDE將會使用諸如arg0、arg1之類的占位符代替原有的參數(shù)名。這對程序運(yùn)行沒有影響,但會對代碼編寫帶來較大不便,而且在調(diào)試期間無法根據(jù)參數(shù)名稱從上下文中獲取參數(shù)值。
LocalVariableTable屬性對于理解程序的執(zhí)行過程以及在調(diào)試中獲取更多有關(guān)局部變量的信息非常有用。
| 字段名 | 類型 | 描述 |
|---|---|---|
| attribute_name_index | u2 | 指向常量池中CONSTANT_Utf8_info類型的屬性名稱 "LocalVariableTable" 的索引 |
| attribute_length | u4 | 屬性值的長度,不包括 attribute_name_index 和 attribute_length 自身的長度 |
| local_variable_table | 表 | 包含多個局部變量項的表,每個局部變量項包括 start_pc、length、name_index、descriptor_index 和 index 字段,表示局部變量在字節(jié)碼中的范圍、名稱、描述符和索引 |
SourceFile 屬性
SourceFile屬性用于記錄生成這個Class文件的源碼文件名稱。這個屬性是可選的,可以使用Javac的-g:none或-g:source選項來關(guān)閉或要求生成這項信息。在大多數(shù)情況下,Java類的類名和文件名是一致的,但是在一些特殊情況(例如內(nèi)部類)下可能存在例外情況。如果不生成這項屬性,當(dāng)拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名。這個屬性是一個定長的屬性。
ourceFile屬性有助于在調(diào)試時追蹤代碼,特別是在涉及多個源文件的項目中。
| 字段名 | 類型 | 描述 |
|---|---|---|
| attribute_name_index | u2 | 指向常量池中CONSTANT_Utf8_info類型的屬性名稱 "SourceFile" 的索引 |
| attribute_length | u4 | 屬性值的長度,不包括 attribute_name_index 和 attribute_length 自身的長度 |
| sourcefile_index | u2 | 指向常量池中CONSTANT_Utf8_info類型的源文件名的索引 |
SourceDebugExtension 屬性
SourceDebugExtension屬性是為了存儲額外的代碼調(diào)試信息,特別是在涉及非Java語言編寫、但需要編譯成字節(jié)碼并在Java虛擬機(jī)中運(yùn)行的程序時。這個屬性的數(shù)據(jù)項是指向常量池中CONSTANT_Utf8_info型常量的索引,該常量的值是源代碼文件的調(diào)試信息。
在JDK 5時,引入了SourceDebugExtension屬性,用于存儲JSR 45提案所定義的標(biāo)準(zhǔn)調(diào)試信息。這對于需要在Java虛擬機(jī)中運(yùn)行的非Java語言編寫的程序提供了一種標(biāo)準(zhǔn)的調(diào)試機(jī)制。典型的場景是在進(jìn)行JSP文件調(diào)試時,由于無法通過Java堆棧來定位到JSP文件的行號,可以使用SourceDebugExtension屬性來存儲額外的調(diào)試信息,使程序員能夠更快速地從異常堆棧中定位到原始JSP中出現(xiàn)問題的行號。
這個屬性在一些特定的情況下很有用,但在一般的Java程序開發(fā)中,由于使用Java語言編寫,通常不需要額外的非Java調(diào)試信息。因此,對于大多數(shù)Java應(yīng)用,可能并不常見。
| 字段名 | 類型 | 描述 |
|---|---|---|
| attribute_name_index | u2 | 指向常量池中CONSTANT_Utf8_info類型的屬性名稱 "SourceDebugExtension" 的索引 |
| attribute_length | u4 | 屬性值的長度,不包括 attribute_name_index 和 attribute_length 自身的長度 |
| debug_extension | 字節(jié)數(shù)組 | 包含調(diào)試信息的字節(jié)數(shù)組 |
還有很多屬性如:不再贅述
AnnotationDefaultBootstrapMethods-
MethodParameters
...
二、字節(jié)碼指令
在Java虛擬機(jī)的指令集中,指令可以分為多個大的類別,以下是其中一些主要的指令類別:
-
加載和存儲指令(Load and Store Instructions):
-
aaload,aastore,baload,bastore,caload,castore,daload,dastore,faload,fastore,iaload,iastore,laload,lastore,saload,sastore, 等。
-
-
操作數(shù)棧管理指令(Stack Management Instructions):
-
pop,pop2,dup,dup_x1,dup_x2,dup2,dup2_x1,dup2_x2,swap, 等。
-
-
數(shù)學(xué)運(yùn)算指令(Arithmetic Instructions):
-
iadd,isub,imul,idiv,irem,iinc,ladd,lsub,lmul,ldiv,lrem,fadd,fsub,fmul,fdiv,frem,dadd,dsub,dmul,ddiv,drem, 等。
-
-
類型轉(zhuǎn)換指令(Type Conversion Instructions):
-
i2l,i2f,i2d,l2i,l2f,l2d,f2i,f2l,f2d,d2i,d2l,d2f,i2b,i2c,i2s, 等。
-
-
比較指令(Comparison Instructions):
-
lcmp,fcmpl,fcmpg,dcmpl,dcmpg,ifcmp<cond>,<cond>,if<cond>, 等。
-
-
控制轉(zhuǎn)移指令(Control Transfer Instructions):
-
goto,tableswitch,lookupswitch,ireturn,lreturn,freturn,dreturn,areturn,return,athrow,jsr,ret,if<cond>, 等。
-
-
引用類和對象的指令(Reference Instructions):
-
new,newarray,anewarray,multianewarray,checkcast,instanceof,getfield,putfield,getstatic,putstatic, 等。
-
-
方法調(diào)用和返回指令(Method Invocation and Return Instructions):
-
invokevirtual,invokespecial,invokestatic,invokeinterface,invokedynamic,return,areturn,ireturn,lreturn,freturn,dreturn, 等。
-
-
異常處理指令(Exception Handling Instructions):
-
athrow,monitorenter,monitorexit,try-catch-finally塊相關(guān)的指令。
-
這些指令構(gòu)成了Java虛擬機(jī)的指令集,用于執(zhí)行Java字節(jié)碼。每個指令都有特定的操作碼和操作數(shù),用于在操作數(shù)棧上執(zhí)行相應(yīng)的操作
字節(jié)碼指令集在Java虛擬機(jī)中具有獨(dú)特的特點和一些限制:
- 操作碼長度限制: 指令集的操作碼被限制為一個字節(jié),范圍為0~255,這意味著指令集的操作碼總數(shù)不能超過256條。這種設(shè)計有助于簡化指令的編碼和解碼過程。
-
操作數(shù)長度對齊: Class文件格式中放棄了編譯后代碼的操作數(shù)長度對齊。這意味著虛擬機(jī)在處理超過一個字節(jié)的數(shù)據(jù)時,需要在運(yùn)行時從字節(jié)中重建具體數(shù)據(jù)的結(jié)構(gòu)。例如,將一個16位長度的無符號整數(shù)存儲在兩個無符號字節(jié)中,需要使用表達(dá)式
(byte1 << 8) | byte2進(jìn)行重建。
這些設(shè)計選擇有一些優(yōu)勢和劣勢:
優(yōu)勢:
- 緊湊性: 一個字節(jié)的操作碼和簡化的操作數(shù)對于Class文件的緊湊性是有利的,減小了字節(jié)碼文件的大小。
- 解析速度: 簡單的指令格式和有限的操作碼范圍有助于提高字節(jié)碼的解析速度。
劣勢:
- 指令數(shù)限制: 256條操作碼的限制可能限制了指令集的豐富性,盡管在實踐中這仍然足夠支持豐富的語義。
- 運(yùn)行時處理成本: 虛擬機(jī)在處理較大的數(shù)據(jù)時需要進(jìn)行運(yùn)行時的計算,可能增加了一些運(yùn)行時的成本。
總體而言,這些設(shè)計選擇是為了在保持緊湊性和解析速度的同時,提供足夠的靈活性來支持Java虛擬機(jī)的執(zhí)行需求。
如果不考慮異常處理的話,那Java虛擬機(jī)的解釋器可以使用下面這段偽代碼作為最基本的執(zhí)行模 型來理解,這個執(zhí)行模型雖然很簡單,但依然可以有效正確地工作
do {
自動計算PC寄存器的值加1;
根據(jù)PC寄存器指示的位置,從字節(jié)碼流中取出操作碼;
if (字節(jié)碼存在操作數(shù))
從字節(jié)碼流中取出操作數(shù);
執(zhí)行操作碼所定義的操作;
} while (字節(jié)碼流長度 > 0);
2.1字節(jié)碼與數(shù)據(jù)類型
如下列舉了Java虛擬機(jī)所支持的與數(shù)據(jù)類型相關(guān)的字節(jié)碼指令,通過使用數(shù)據(jù)類型列所代表的特殊字符替換opcode列的指令模板中的T,就可以得到一個具體的字節(jié)碼指令。
如果在表中指令模板與數(shù)據(jù)類型兩列共同確定的格為空,則說明虛擬機(jī)不支持對這種數(shù)據(jù)類型執(zhí)行這項操作。例如load指令有操作int類型的iload,但是沒有操作byte類型的同類指令。
Java虛擬機(jī)的字節(jié)碼指令集并沒有提供專門用于處理整數(shù)類型`byte`、`char`和`short`以及布爾類型(`boolean`)的指令。相反,編譯器在編譯期或運(yùn)行期進(jìn)行類型轉(zhuǎn)換,將這些較小的整數(shù)類型轉(zhuǎn)換為`int`類型,然后使用`int`類型的字節(jié)碼指令來進(jìn)行操作。具體而言:
-
帶符號擴(kuò)展(Sign-Extend): 對于
byte和short類型,編譯器會進(jìn)行帶符號擴(kuò)展,將它們轉(zhuǎn)換為相應(yīng)的int類型。這意味著,如果原始值是負(fù)數(shù),它會被符號擴(kuò)展為32位帶符號整數(shù)。 -
零位擴(kuò)展(Zero-Extend): 對于
boolean和char類型,同樣會進(jìn)行零位擴(kuò)展,將它們轉(zhuǎn)換為相應(yīng)的int類型。這意味著,無論原始值是什么,都會被零位擴(kuò)展為32位無符號整數(shù)。
在處理boolean、byte、short和char類型的數(shù)組時,也會使用對應(yīng)的int類型的字節(jié)碼指令來進(jìn)行操作。因此,實際上,大多數(shù)對于這些較小整數(shù)類型的操作,都是使用int類型作為運(yùn)算類型來進(jìn)行的。這種設(shè)計簡化了字節(jié)碼指令集,減少了復(fù)雜性。
2.2加載和存儲指令
加載和存儲指令在Java虛擬機(jī)中用于在棧幀的局部變量表和操作數(shù)棧之間傳輸數(shù)據(jù)。這些指令包括:
-
將一個局部變量加載到操作數(shù)棧:
-
iload:將int類型的局部變量加載到操作數(shù)棧。 -
iload_<n>:將int類型的局部變量加載到操作數(shù)棧,其中<n>表示局部變量索引,可以是0到3的數(shù)字。
(類似的指令存在于其他數(shù)據(jù)類型,如
lload、fload、dload、aload) -
-
將一個數(shù)值從操作數(shù)棧存儲到局部變量表:
-
istore:將int類型的數(shù)值存儲到局部變量表。 -
istore_<n>:將int類型的數(shù)值存儲到局部變量表,其中<n>表示局部變量索引,可以是0到3的數(shù)字。
(類似的指令存在于其他數(shù)據(jù)類型,如
lstore、fstore、dstore、astore) -
-
將一個常量加載到操作數(shù)棧:
-
bipush:將單字節(jié)常量(-128到127之間的整數(shù))推送到操作數(shù)棧。 -
sipush:將短整型常量(-32768到32767之間的整數(shù))推送到操作數(shù)棧。 -
ldc:將int、float或String類型的常量值從常量池中推送到操作數(shù)棧。 -
ldc_w:與ldc類似,但用于更大的常量池索引。
(其他指令用于加載更大的常量,如
ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>) -
-
擴(kuò)充局部變量表的訪問索引的指令:
-
wide:用于擴(kuò)大對局部變量表的訪問索引,通常與其他指令一起使用。
-
一些指令的助記符以尖括號結(jié)尾,表示這是一組指令的特殊形式。例如,
iload_<n>表示了一組特殊的iload指令,其中<n>可以是0到3的數(shù)字。這些特殊指令省略了顯式的操作數(shù),因為操作數(shù)隱含在指令中。這些指令的語義與原生的通用指令完全一致。
2.3運(yùn)算指令
Java虛擬機(jī)的算術(shù)指令用于對兩個操作數(shù)棧上的值進(jìn)行特定運(yùn)算,并將結(jié)果重新存入操作數(shù)棧頂。主要分為對整型數(shù)據(jù)和浮點型數(shù)據(jù)的運(yùn)算,其中涵蓋了加法、減法、乘法、除法、求余、取反、位移、按位或、按位與、按位異或、局部變量自增、比較等操作。
以下是具體的算術(shù)指令列表:
整數(shù)運(yùn)算指令(對應(yīng)不同數(shù)據(jù)類型,如int、long):
- 加法指令:
iadd、ladd - 減法指令:
isub、lsub - 乘法指令:
imul、lmul - 除法指令:
idiv、ldiv - 求余指令:
irem、lrem - 取反指令:
ineg、lneg - 位移指令:
ishl、ishr、iushr、lshl、lshr、lushr - 按位或指令:
ior、lor - 按位與指令:
iand、land - 按位異或指令:
ixor、lxor - 局部變量自增指令:
iinc - 比較指令:
dcmpg、dcmpl、fcmpg、fcmpl、lcmp
浮點數(shù)運(yùn)算指令(對應(yīng)不同數(shù)據(jù)類型,如float、double):
- 加法指令:
fadd、dadd - 減法指令:
fsub、dsub - 乘法指令:
fmul、dmul - 除法指令:
fdiv、ddiv - 求余指令:
frem、drem - 取反指令:
fneg、dneg
在整型數(shù)據(jù)溢出的情況下,虛擬機(jī)規(guī)范并未定義具體的結(jié)果,只有在除法和求余指令中當(dāng)除數(shù)為零時會拋出
ArithmeticException異常。對于浮點數(shù)運(yùn)算,虛擬機(jī)要求遵循IEEE 754規(guī)范,包括對非正規(guī)浮點數(shù)值和逐級下溢的運(yùn)算規(guī)則。在對long類型數(shù)值進(jìn)行比較時,采用帶符號的比較方式;而對浮點數(shù)值進(jìn)行比較時,采用IEEE 754規(guī)范中的無信號比較方式。
如果某個操作結(jié)果沒有明確的數(shù)學(xué)定義的話, 將會使用NaN(Not a Number)值來表示。所有使用NaN值作為操作數(shù)的算術(shù)操作,結(jié)果都會返回NaN。
這些規(guī)定確保了在Java虛擬機(jī)中進(jìn)行數(shù)值運(yùn)算時,結(jié)果是符合預(yù)期并具有可靠性的。
2.4類型轉(zhuǎn)換指令
類型轉(zhuǎn)換指令用于將兩種不同的數(shù)值類型相互轉(zhuǎn)換,主要分為寬化類型轉(zhuǎn)換(Widening Numeric Conversion)和窄化類型轉(zhuǎn)換(Narrowing Numeric Conversion)兩種。
Java虛擬機(jī)直接支持寬化類型轉(zhuǎn)換,即將小范圍類型向大范圍類型進(jìn)行安全轉(zhuǎn)換。例如:
- 將int類型轉(zhuǎn)換為long、float或double類型
- 將long類型轉(zhuǎn)換為float或double類型
- 將float類型轉(zhuǎn)換為double類型
窄化類型轉(zhuǎn)換必須顯式地使用轉(zhuǎn)換指令完成,包括:
-
i2b:將int類型轉(zhuǎn)換為byte類型 -
i2c:將int類型轉(zhuǎn)換為char類型 -
i2s:將int類型轉(zhuǎn)換為short類型 -
l2i:將long類型轉(zhuǎn)換為int類型 -
f2i:將float類型轉(zhuǎn)換為int類型 -
f2l:將float類型轉(zhuǎn)換為long類型 -
d2i:將double類型轉(zhuǎn)換為int類型 -
d2l:將double類型轉(zhuǎn)換為long類型 -
d2f:將double類型轉(zhuǎn)換為float類型
窄化類型轉(zhuǎn)換可能導(dǎo)致轉(zhuǎn)換結(jié)果的正負(fù)號變化以及數(shù)值的精度丟失。在浮點數(shù)值窄化轉(zhuǎn)換為整數(shù)類型時,需遵循一定規(guī)則,如對NaN的處理和使用IEEE 754的向零舍入模式取整。虛擬機(jī)規(guī)范明確規(guī)定數(shù)值類型的窄化轉(zhuǎn)換指令不會導(dǎo)致運(yùn)行時異常。
這些規(guī)定確保了在Java虛擬機(jī)中進(jìn)行數(shù)值類型轉(zhuǎn)換時,能夠預(yù)期并具有可靠性的結(jié)果。
2.5對象創(chuàng)建與訪問指令
Java虛擬機(jī)對類實例和數(shù)組的創(chuàng)建與操作使用了不同的字節(jié)碼指令。以下是涉及對象創(chuàng)建和操作的一些指令:
創(chuàng)建類實例的指令:
-
new:創(chuàng)建一個新的類實例
創(chuàng)建數(shù)組的指令:
-
newarray:創(chuàng)建一個基本類型數(shù)組 -
anewarray:創(chuàng)建一個引用類型數(shù)組 -
multianewarray:創(chuàng)建一個多維數(shù)組
訪問類字段和實例字段的指令:
-
getfield:獲取實例字段的值 -
putfield:設(shè)置實例字段的值 -
getstatic:獲取類字段(靜態(tài)字段)的值 -
putstatic:設(shè)置類字段(靜態(tài)字段)的值
數(shù)組元素的加載和存儲指令:
-
baload:將一個byte或boolean數(shù)組元素加載到操作數(shù)棧 -
caload:將一個char數(shù)組元素加載到操作數(shù)棧 -
saload:將一個short數(shù)組元素加載到操作數(shù)棧 -
iaload:將一個int數(shù)組元素加載到操作數(shù)棧 -
laload:將一個long數(shù)組元素加載到操作數(shù)棧 -
faload:將一個float數(shù)組元素加載到操作數(shù)棧 -
daload:將一個double數(shù)組元素加載到操作數(shù)棧 -
aaload:將一個引用類型數(shù)組元素加載到操作數(shù)棧 -
bastore:將一個byte或boolean值存儲到byte或boolean數(shù)組元素中 -
castore:將一個char值存儲到char數(shù)組元素中 -
sastore:將一個short值存儲到short數(shù)組元素中 -
iastore:將一個int值存儲到int數(shù)組元素中 -
lastore:將一個long值存儲到long數(shù)組元素中 -
fastore:將一個float值存儲到float數(shù)組元素中 -
dastore:將一個double值存儲到double數(shù)組元素中 -
aastore:將一個引用類型值存儲到引用類型數(shù)組元素中
數(shù)組長度的指令:
-
arraylength:獲取數(shù)組的長度
檢查類實例類型的指令:
-
instanceof:檢查對象是否是某個類的實例 -
checkcast:檢查對象是否可以強(qiáng)制轉(zhuǎn)換為指定類型
2.6操作數(shù)棧管理指令
Java虛擬機(jī)提供了一些指令,用于直接操作操作數(shù)棧。這些指令包括:
將操作數(shù)棧的棧頂一個或兩個元素出棧:
-
pop:將棧頂一個元素彈出 -
pop2:將棧頂兩個元素彈出
復(fù)制棧頂一個或兩個數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:
-
dup:復(fù)制棧頂一個元素并將復(fù)制值重新壓入棧頂 -
dup2:復(fù)制棧頂兩個元素并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂 -
dup_x1:復(fù)制棧頂一個元素并將復(fù)制值與棧頂下面的元素互換位置,然后重新壓入棧頂 -
dup2_x1:復(fù)制棧頂兩個元素并將復(fù)制值或雙份的復(fù)制值與棧頂下面的元素互換位置,然后重新壓入棧頂 -
dup_x2:復(fù)制棧頂一個元素并將復(fù)制值與棧頂下面的兩個元素互換位置,然后重新壓入棧頂 -
dup2_x2:復(fù)制棧頂兩個元素并將復(fù)制值或雙份的復(fù)制值與棧頂下面的兩個元素互換位置,然后重新壓入棧頂
將棧最頂端的兩個數(shù)值互換:
-
swap:將棧最頂端的兩個元素互換位置
2.7控制轉(zhuǎn)移指令
控制轉(zhuǎn)移指令在Java虛擬機(jī)中用于有條件或無條件地改變程序執(zhí)行流程。這些指令包括:
條件分支:
-
ifeq:如果棧頂元素等于0,則跳轉(zhuǎn) -
iflt:如果棧頂元素小于0,則跳轉(zhuǎn) -
ifle:如果棧頂元素小于等于0,則跳轉(zhuǎn) -
ifne:如果棧頂元素不等于0,則跳轉(zhuǎn) -
ifgt:如果棧頂元素大于0,則跳轉(zhuǎn) -
ifge:如果棧頂元素大于等于0,則跳轉(zhuǎn) -
ifnull:如果棧頂元素為null,則跳轉(zhuǎn) -
ifnonnull:如果棧頂元素不為null,則跳轉(zhuǎn) -
if_icmpeq:如果棧頂兩個int型元素相等,則跳轉(zhuǎn) -
if_icmpne:如果棧頂兩個int型元素不相等,則跳轉(zhuǎn) -
if_icmplt:如果棧頂兩個int型元素第一個小于第二個,則跳轉(zhuǎn) -
if_icmpgt:如果棧頂兩個int型元素第一個大于第二個,則跳轉(zhuǎn) -
if_icmple:如果棧頂兩個int型元素第一個小于等于第二個,則跳轉(zhuǎn) -
if_icmpge:如果棧頂兩個int型元素第一個大于等于第二個,則跳轉(zhuǎn) -
if_acmpeq:如果棧頂兩個引用類型元素相等,則跳轉(zhuǎn) -
if_acmpne:如果棧頂兩個引用類型元素不相等,則跳轉(zhuǎn)
復(fù)合條件分支:
-
tableswitch:通過索引訪問表格來進(jìn)行跳轉(zhuǎn),用于switch語句的實現(xiàn) -
lookupswitch:通過鍵值對訪問表格來進(jìn)行跳轉(zhuǎn),用于switch語句的實現(xiàn)
無條件分支:
-
goto:無條件跳轉(zhuǎn) -
goto_w:無條件跳轉(zhuǎn)(寬索引) -
jsr:跳轉(zhuǎn)到子例程(調(diào)用子例程) -
jsr_w:跳轉(zhuǎn)到子例程(調(diào)用子例程,寬索引) -
ret:返回子例程
寬索引是使用4個字節(jié)而不是標(biāo)準(zhǔn)的1個字節(jié)來表示跳轉(zhuǎn)目標(biāo)的偏移量。這使得這兩個指令能夠處理更大范圍的代碼偏移,允許跳轉(zhuǎn)到更遠(yuǎn)的位置。
2.8方法調(diào)用和返回指令
在Java虛擬機(jī)的指令集中,方法調(diào)用是通過一系列不同的指令完成的,這些指令涵蓋了不同類型的方法調(diào)用。以下是五個主要的方法調(diào)用指令:
-
invokevirtual指令:
- 用于調(diào)用對象的實例方法。
- 根據(jù)對象的實際類型進(jìn)行分派,這是虛方法分派的典型方式。
- 是Java語言中最常見的方法分派方式。
虛方法分派(Virtual Method Dispatch)是指在面向?qū)ο缶幊讨校鶕?jù)對象的實際類型(運(yùn)行時類型)來確定調(diào)用哪個版本的方法。這種分派方式主要用于處理多態(tài)性,確保在運(yùn)行時調(diào)用的是對象實際所屬類的方法,而不是編譯時所聲明的類型。
是面向?qū)ο缶幊讨袑崿F(xiàn)多態(tài)的重要機(jī)制之一。
-
invokeinterface指令:
- 用于調(diào)用接口方法。
- 在運(yùn)行時搜索一個實現(xiàn)了這個接口方法的對象,找出適合的方法進(jìn)行調(diào)用。
-
invokespecial指令:
- 用于調(diào)用一些需要特殊處理的實例方法。
- 包括實例初始化方法、私有方法和父類方法。
-
invokestatic指令:
- 用于調(diào)用類靜態(tài)方法(static方法)。
-
invokedynamic指令:
- 用于在運(yùn)行時動態(tài)解析出調(diào)用點限定符所引用的方法,并執(zhí)行該方法。
- 與前四條調(diào)用指令不同,invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導(dǎo)方法決定的。
方法調(diào)用指令與數(shù)據(jù)類型無關(guān)。方法的返回操作則根據(jù)返回值的類型有不同的指令,包括:
-
ireturn(用于返回boolean、byte、char、short和int類型的值), -
lreturn(long類型的值), -
freturn(float類型的值), -
dreturn(double類型的值), -
areturn(引用類型的值)。
此外,還有一條return指令,供聲明為void的方法、實例初始化方法、類和接口的類初始化方法使用。
2.9異常處理指令
在Java虛擬機(jī)中,athrow 指令用于顯式拋出異常。當(dāng)在程序中使用 throw 語句時,編譯器會將相應(yīng)的異常對象推送到操作數(shù)棧頂,然后通過 athrow 指令將異常拋出。athrow 指令的使用類似于其他指令,只不過它專門用于拋出異常。
異常處理(catch語句)不是由特定的字節(jié)碼指令來實現(xiàn)的,而是通過異常表(Exception Table)來完成。異常表是一種數(shù)據(jù)結(jié)構(gòu),用于在方法的字節(jié)碼中記錄異常處理器的信息,包括受監(jiān)控的范圍、捕獲的異常類型以及對應(yīng)的異常處理代碼的起始位置等信息。
異常表的作用是在方法的字節(jié)碼執(zhí)行過程中,當(dāng)發(fā)生異常時,虛擬機(jī)會根據(jù)異常表中的信息確定如何處理異常。以下是異常表的主要結(jié)構(gòu):
- start_pc、end_pc: 定義了受監(jiān)控范圍的起始和結(jié)束位置。在這個范圍內(nèi),如果發(fā)生異常,則按照異常表中的處理器信息進(jìn)行處理。
- handler_pc: 指定了異常處理器的起始位置,即對應(yīng)異常發(fā)生時要執(zhí)行的代碼的入口。
-
catch_type: 指定了捕獲的異常類型,是一個對常量池中CONSTANT_Class_info型常量的索引,表示捕獲的異常類型。如果catch_type的值為0,表示捕獲所有類型的異常(相當(dāng)于Java中的
catch(Exception e))。
異常表中的每一項都對應(yīng)著一個異常處理器,Java虛擬機(jī)在發(fā)現(xiàn)異常時會遍歷異常表,找到第一個匹配的異常處理器,然后跳轉(zhuǎn)到相應(yīng)的處理代碼塊。如果沒有找到匹配的異常處理器,那么異常將會傳遞到上層調(diào)用棧。
2.10同步指令
字節(jié)碼指令在Java虛擬機(jī)中的執(zhí)行是原子性的。每個字節(jié)碼指令都被視為一個原子操作,它們要么完全執(zhí)行,要么不執(zhí)行。這種原子性保證了在多線程環(huán)境中,一個線程執(zhí)行的字節(jié)碼指令不會被其他線程中斷或插入。
但是代碼指令則為非原子性,例如讀取和寫入共享變量。在多線程環(huán)境下,為了確保線程安全,可能需要使用額外的同步機(jī)制。
因此,Java虛擬機(jī)支持方法級的同步和方法內(nèi)部一段指令序列的同步,這兩種同步結(jié)構(gòu)都使用管程(Monitor,通常稱為“鎖”)來實現(xiàn)。
-
方法級的同步(隱式同步): 方法級的同步是隱式的,無需通過字節(jié)碼指令控制。虛擬機(jī)可以通過檢查方法的訪問標(biāo)志(ACC_SYNCHRONIZED)來確定一個方法是否被聲明為同步方法。
- 當(dāng)調(diào)用同步方法時,調(diào)用指令檢查方法的訪問標(biāo)志,如果設(shè)置了,執(zhí)行線程要求首先成功持有管程(鎖),然后才能執(zhí)行方法。最后,在方法完成時,無論是正常完成還是非正常完成,都會釋放管程。
- 在同步方法執(zhí)行期間,執(zhí)行線程持有管程,其他線程無法獲取相同的管程。如果同步方法執(zhí)行期間拋出異常,并且在方法內(nèi)部無法處理此異常,同步方法所持有的管程將在異常拋到同步方法邊界之外時自動釋放。
-
同步一段指令序列: 同步一段指令序列通常由Java語言中的
synchronized語句塊表示。- Java虛擬機(jī)提供了
monitorenter和monitorexit兩條指令來支持synchronized關(guān)鍵字的語義。 - 這種同步方式需要Javac編譯器與Java虛擬機(jī)共同協(xié)作來支持,舉例如下:
void onlyMe(Foo f) { synchronized(f) { doSomething(); } }? 編譯后,這段代碼生成的字節(jié)碼序列如下:
Method void onlyMe(Foo) 0 aload_1 // 將對象f入棧 1 dup // 復(fù)制棧頂元素(即f的引用) 2 astore_2 // 將棧頂元素存儲到局部變量表變量槽2中 3 monitorenter // 以棧頂元素(即f)作為鎖,開始同步 4 aload_0 // 將局部變量槽0(即this指針)的元素入棧 5 invokevirtual #5 // 調(diào)用doSomething()方法 8 aload_2 // 將局部變量槽2的元素(即f)入棧 9 monitorexit // 退出同步 10 goto 18 // 方法正常結(jié)束,跳轉(zhuǎn)到18返回 13 astore_3 // 從這步開始是異常路徑,見下面異常表的Target 14 aload_2 // 將局部變量槽2的元素(即f)入棧 15 monitorexit // 退出同步 16 aload_3 // 將局部變量槽3的元素(即異常對象)入棧 17 athrow // 把異常對象重新拋出給onlyMe()方法的調(diào)用者 18 return // 方法正常返回為了保證在方法異常完成時monitorenter和monitorexit指 令依然可以正確配對執(zhí)行,編譯器會自動產(chǎn)生一個異常處理程序,這個異常處理程序聲明可處理所有的異常,它的目的就是用來執(zhí)行monitorexit指令。
- Java虛擬機(jī)提供了
三、公有設(shè)計、私有實現(xiàn)
Java虛擬機(jī)規(guī)范對于Java程序與虛擬機(jī)實現(xiàn)之間的關(guān)系的規(guī)定。它明確了虛擬機(jī)實現(xiàn)者在設(shè)計虛擬機(jī)時的*度和靈活性。一些關(guān)鍵點包括:
- 公有設(shè)計與私有實現(xiàn)之分界線: Java虛擬機(jī)規(guī)范定義了Java虛擬機(jī)應(yīng)有的共同程序存儲格式(Class文件格式)和字節(jié)碼指令集。這些規(guī)范為Java平臺上的不同實現(xiàn)提供了一個通用的交互手段。規(guī)范強(qiáng)調(diào)了實現(xiàn)者可以靈活地在實現(xiàn)中進(jìn)行優(yōu)化和修改,只要保持對Class文件的正確讀取和包含在其中的語義的準(zhǔn)確實現(xiàn)。
- 實現(xiàn)的伸縮性: 實現(xiàn)者可以根據(jù)虛擬機(jī)的目標(biāo)和關(guān)注點選擇不同的實現(xiàn)方式。這包括將Java虛擬機(jī)代碼翻譯成另一種虛擬機(jī)的指令集或?qū)⑵浞g成宿主機(jī)處理程序的本地指令集。這種伸縮性使得虛擬機(jī)可以在性能、內(nèi)存消耗和可移植性等方面進(jìn)行權(quán)衡和優(yōu)化。
- 即時編譯器(Just-In-Time Compiler)等例外情況: 在某些情況下,一些工具如調(diào)試器、性能監(jiān)視器和即時編譯器可能需要訪問一些通常被認(rèn)為是虛擬機(jī)后臺的元素,這可能對實現(xiàn)者的*度產(chǎn)生一些限制。
虛擬機(jī)實現(xiàn)者有很大的靈活性來調(diào)整實現(xiàn)以提高性能、降低內(nèi)存消耗或?qū)崿F(xiàn)其他目標(biāo),同時保持對Java虛擬機(jī)規(guī)范的兼容性。這種設(shè)計理念為不同的Java虛擬機(jī)實現(xiàn)提供了空間,以滿足各種不同的需求。
四、Class文件結(jié)構(gòu)的發(fā)展
Class文件結(jié)構(gòu)在Java技術(shù)體系中具有穩(wěn)定性和可擴(kuò)展性。以下是一些重要的觀點:
- Class文件結(jié)構(gòu)的穩(wěn)定性: 自《Java虛擬機(jī)規(guī)范》初版訂立以來,Class文件結(jié)構(gòu)已經(jīng)有二十多年的歷史。在這段時間里,盡管Java技術(shù)體系發(fā)生了巨大的改變,包括語言、API等方面的變化,但是Class文件結(jié)構(gòu)一直保持相對穩(wěn)定,主體結(jié)構(gòu)和字節(jié)碼指令的語義和數(shù)量幾乎沒有變動。
- 對訪問標(biāo)志和屬性表的改進(jìn): 隨著Java技術(shù)的演進(jìn),Class文件的訪問標(biāo)志和屬性表也進(jìn)行了一些改進(jìn)。訪問標(biāo)志新增了一些標(biāo)志,如ACC_SYNTHETIC、ACC_ANNOTATION、ACC_ENUM、ACC_BRIDGE、ACC_VARARGS。屬性表集合中新增了一系列屬性,主要用于支持新的語言特性,如枚舉、變長參數(shù)、泛型、動態(tài)注解等,以及為了性能改進(jìn)和調(diào)試信息。
- 平臺中立和可擴(kuò)展性的重要性: Class文件格式具有平臺中立、緊湊、穩(wěn)定和可擴(kuò)展的特點,這是實現(xiàn)Java技術(shù)體系中平臺無關(guān)和語言無關(guān)兩項特性的關(guān)鍵支柱。這種設(shè)計使得Java程序可以在不同的硬件和操作系統(tǒng)上運(yùn)行,同時為未來的語言特性和擴(kuò)展提供了空間。
二十余年間,字節(jié)碼的數(shù)量和語義只發(fā)生過屈指可數(shù)的幾次變動,例如JDK1.0.2時改動過invokespecial指令的語義,JDK 7增加了invokedynamic指令,禁止了ret和jsr指令。
總結(jié)
以上是生活随笔為你收集整理的JVM学习-Class文件结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文心一言 VS 讯飞星火 VS chat
- 下一篇: 简易机器学习笔记(十一)opencv 简