這篇文章主要內容來自"深入java虛擬機器",剛畢業那會太急功近利,這塊離業務程式碼太遠就沒細看。這次花點時間整理一下,加深對位元組碼的認識。
類檔案結構
“一次編寫,到處執行”表達的是“與平臺無關”。如何做到“與平臺無關”?Sun公司以及其他虛擬機器提供商釋出了許多可以執行在各種平臺上的虛擬機器,這些虛擬機器都可以載入和執行同一種平臺無關的位元組碼(class檔案)。
Java虛擬機器的野心遠不止做到“與平臺無關”,Java虛擬機器還做到“語言無關性”。Java虛擬機器支援其他語言的程式碼通過對應語言的編譯器,編譯成位元組碼,從而執行在Java虛擬機器之上。
Class檔案是一組以8位位元組為基礎單元的二進位制流,各個資料專案嚴格按照順序緊湊地排列在Class檔案之中,中間沒有新增任何分隔符。Class檔案格式採用一種類似於C語言結構體的偽結構來儲存資料,這種偽結構只有兩種資料型別:無符號數 和 表。
表1:Class檔案格式定義
名稱 |
型別 |
數量 |
描述 |
---|
magic |
u4 |
1 |
class檔案的識別碼,固定,為“0xCAFFBABE” |
minor_version |
u2 |
1 |
次版本號,JDK1.7_u91,其中91就是次版本號 |
major_version |
u2 |
1 |
主版本號,JDK1.7對應值為51,JDK1.8對應值為52,依次類推。虛擬機器拒絕執行超過其版本號的Class檔案 |
constant_pool_count |
u2 |
1 |
常量池中常量的數量,從1開始計數 |
constant_pool |
cp_info |
constant_pool_count |
佔用Class檔案空間最大的資料項。常量池中存放兩大常量:字面量(java中的常量,如文字字串、宣告為final的常量值等)和符號引用(類以及介面的全限定名、欄位的名稱和描述符、方法的名稱和描述符)。cp_info有14種型別結構,通過開頭的u1型別的標誌位來表示,每一種型別的具體格式見 表2:常量池結構表
|
access_flags |
u2 |
1 |
訪問標識,用於識別class還是介面、是否定義為public、是否定義為abstract、是否被宣告為final等,u2總共16位,每一位可以代表一類資訊是否。具體就不闡述了 |
this_class |
u2 |
1 |
類索引,一個指向常量型別為CONSTANT_UTF8_info的全限定名字串 |
super_class |
u2 |
1 |
父類索引,一個指向常量型別為CONSTANT_UTF8_info的全限定名字串 |
interfaces_count |
u2 |
1 |
繼承介面數量 |
interfaces |
u2 |
interfaces_count |
介面索引,一個指向常量型別為CONSTANT_UTF8_info的全限定名字串 |
fields_count |
u2 |
1 |
欄位數量,包括類級變數以及例項級數量,但不包括方法內部的區域性變數 |
fields |
field_info |
fields_count |
欄位表結構包括5個型別:access_flag(和類的access_flag類似)、name_index(欄位的簡單名稱,指向常量池索引)、descriptor_index(方法的描述符,描述欄位的資料型別、方法的引數列表和返回值,對於資料型別,每一個維度使用一個前置的“[”字元描述,基礎型別使用第一個字元大寫,物件型別使用L+類全限定符,方法int indexOf(char[] source, Object target)的描述符常量為([CLjava/lang/Object)I )、attributes_count(屬性的數量)、attribute_info(和類的attribute_info一樣) |
methods_count |
u2 |
1 |
方法的數量 |
methods |
method_info |
methods_count |
方法的表結構和欄位的描述是完全一致的,方法裡的java程式碼,經過編譯器編譯成位元組碼指令後,存放在方法屬性表集合中的一個名為“Code”的屬性裡面。屬性的描述見下行 |
attributes_count |
u2 |
1 |
屬性的數量 |
attributes |
attribute_info |
arrtributes_count |
在Class檔案、欄位表、方法表都可以攜帶自己的屬性表集合。目前虛擬機器能識別的屬性型別有21中,見 表3:虛擬機器規範預定義的屬性。屬性表結構包含attribute_name_index(屬性型別名稱,指向常量池的索引)、attribute_length(屬性長度,為屬性表總長度-6)、info(屬性資訊,各屬性型別自定義的屬性結構) |
表2:常量池結構表
型別 |
標誌 |
描述 |
---|
CONSTANT_Utf8_info |
1 |
Utf-8 編碼的字串。1個u1型別的tag,值為1,一個u2型別表示字元長度的length;length個u1型別的bytes。 |
CONSTANT_Integer_info |
3 |
整形字面量。1個u1型別的tag,值為3;1個u4型別的int值 |
CONSTANT_Float_info |
4 |
浮點型字面量。1個u1型別的tag,值為4;1個u4型別的float值 |
CONSTANT_Long_info |
5 |
長整型字面量。1個u1型別的tag,值為5;1個u8型別的long值 |
CONSTANT_Double_info |
6 |
雙精度浮點型字面量。1個u1型別的tag,值為6;1個u8型別的double值 |
CONSTANT_Class_info |
7 |
類或介面的符號引用。一個u1型別的tag,值為7;1個u2型別的name_index,指向CONSTANT_Utf8_info的常量 |
CONSTANT_String_info |
8 |
字串型別字面量。一個u1型別的tag,值為8;1個u2型別的索引,指向CONSTANT_Utf8_info的常量 |
CONSTANT_Fieldref_info |
9 |
欄位的符號引用。一個u1型別的tag,值為9;1個u2型別的索引,指向宣告欄位的類或者介面描述符CONSTANT_Class_info的索引項;1個u2型別的索引,指向欄位描述符CONSTANT_NameAndType的索引項 |
CONSTANT_Methodref_info |
10 |
類中方法的符號引用。一個u1型別的tag,值為10;1個u2型別的索引,指向宣告方法的類描述符CONSTANT_Class_info的索引項;1個u2型別的索引,指向名稱及型別描述符CONSTANT_NameAndType的索引項 |
CONSTANT_InterfaceMethodref_info |
11 |
介面中方法的符號引用。一個u1型別的tag,值為11;1個u2型別的索引,指向宣告方法的介面描述符CONSTANT_Class_info的索引項;1個u2型別的索引,指向名稱及型別描述符 |
CONSTANT_NameAndType_info |
12 |
欄位或方法的部分符號引用。一個u1型別的tag,值為12;1個u2型別的索引,指向該欄位或方法名稱常量項的索引;1個u2型別的索引,指向該欄位或方法描述符常量項的索引 |
CONSTANT_MethodHandle_info |
15 |
表示方法控制程式碼。一個u1型別的tag,值為15;1個u1型別區間為1~9的值,表示方法控制程式碼型別(方法控制程式碼型別的值表示方法控制程式碼的位元組碼行為);1個u2型別的索引,指向常量池 |
CONSTANT_MethodType_info |
16 |
標識方法型別。一個u1型別的tag,值為16;一個u2型別的描述符索引,指向CONSTANT_Utf8_info型別的索引項 |
CONSTANT_InvokeDynamic_info |
18 |
表示一個動態方法呼叫點。一個u1型別的tag,值為18;一個u2型別的索引,指向當前Class檔案中引導方法表的bootstrap_methods[]資料;1個u2型別的索引,指向常量池中型別為CONSTANT_NameAndType_info的索引項 |
表3:虛擬機器規範預定義的屬性
屬性名稱 |
使用位置 |
含義 |
---|
Code |
方法表 |
Java程式碼編譯成的位元組碼指令。Code屬性的結構 表4:Code屬性表的結構
|
ConstantValue |
欄位表 |
final關鍵字定義的常量值。屬性結構為1個u2型別指向常量池的變數名索引;1個u4型別,固定值為2;1個指向常量池的屬性變數值索引。 |
Deprecated |
類、方法表、欄位表 |
被宣告為deprecated的方法和欄位。1個u2型別的執行常量池的名稱索引。1個u4型別,值固定為0的資料值。 |
Exception |
方法表 |
方法丟擲的異常,列舉出方法中可能丟擲的受檢查異常。包含1個u2型別表示可能丟擲異常的種類number_of_exception,和number_of_exception個指向常量池中CONSTANT_CLASS_info型常量的索引 |
EnclosingMethod |
類檔案 |
僅當一個類為區域性類或者匿名類時才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法 |
InnerClasses |
類檔案 |
內部類列表,用於記錄內部類與宿主類之間的關聯。 |
LineNumberTable |
Code屬性 |
Java原始碼的行號與位元組碼指令的對應關係 |
LocalVariableTable |
Code屬性 |
方法的區域性變數描述,描述棧幀中區域性變數表中區域性變數表中的變數與java原始碼中定義的變數之間的關係。包含一個u2型別代表本地變數表長度的值local_variable_table_length,和local_variable_table_length個代表棧幀與原始碼中區域性變數關聯的local_variable_info結構(1個u2型別代表區域性變數生命週期開始的位元組碼偏移量start_pc,1個u2型別代表區域性變數覆蓋範圍的位元組碼長度,1個u2型別指向CONSTANT_Utf8_info索引代表變數名稱和1個u2型別指向CONSTANT_Utf8_info索引代表變數描述符,1個u2型別代表區域性變數在棧幀區域性變數表中slot的位置 ) |
StackMapTable |
Code屬性 |
JDK1.6中新增的屬性,供新的型別檢查驗證器檢查和處理目標方法的區域性變數和運算元棧所需要的型別是否匹配 |
Signature |
類、方法表、欄位表 |
JDK1.5中新增的屬性,這個屬性用於支援泛型情況下的方法簽名,在java語言中,任何類、介面、初始化方法或成員的泛型簽名如果包含了型別變數或引數化型別,則Signarure屬性會為它記錄泛型簽名資訊。 |
SourceFile |
類檔案 |
記錄原始檔名稱。一般情況下,類名和檔名相同 |
SourceDebugExtension |
類檔案 |
JDK1.6中新增的屬性,這個屬性用於儲存額外的除錯資訊 |
Synthetic |
類、方法表、欄位表 |
標識方法或字典為編譯器自動生成的。1個u2型別的執行常量池的名稱索引。1個u4型別,值固定為0的資料。 |
LocalVariableTypeTable |
類 |
JDK1.5新增的屬性,它使用特徵簽名代替描述符,是為了引入泛型語法之後能描述泛型引數化型別而新增 |
RuntimeVisibleAnnotations |
類、方法表、欄位表 |
JDK1.5中新增的屬性,為動態註解提供支援。這個屬性用於指明哪些註解是執行時(實際上執行時就是進行反射呼叫)可見的 |
RuntimeInvisibleAnnotations |
類、方法表、欄位表 |
與RuntimeVisibleAnnotations相反,指明哪些註解執行時不可見 |
RuntimeVisibleParameterAnnotations |
方法表 |
和RuntimeVisibleAnnotations作用類似,只不過作用物件為方法引數 |
RuntimeInvisibleParameterAnnotations |
方法表 |
和RuntimeInvisibleAnnotations作用類似,只不過作用物件為方法引數 |
AnnotationDefault |
方法表 |
JDK1.5新增屬性,用於記錄註解元素的預設值 |
BootstrapMethods |
類檔案 |
JDK1.7新增屬性,用於儲存invokedynamic指令所引用的引導方法限定符 |
表4:Code屬性表的結構
型別 |
名稱 |
數量 |
描述 |
---|
u2 |
attribute_name_index |
1 |
一個指向CONSTANT_Utf8_info型常量的索引,固定值為“Code” |
u4 |
attribute_length |
1 |
屬性值的長度,整個屬性表長度減去6位元組 |
u2 |
max_stack |
1 |
代表運算元棧深度的最大值 |
u2 |
max_locals |
1 |
代表區域性變數表所需的儲存空間,單位slot(slot是虛擬機器為區域性變數分配記憶體所使用的最小單位,4位元組) |
u4 |
code_length |
1 |
位元組碼指令的個數 |
u1 |
code |
code_length |
用於儲存位元組碼指令的一系列位元組流,每個位元組碼指令是一個u1型別的單位元組 |
u2 |
exception_table_length |
1 |
異常表長度 |
exception_info |
exception_table |
exception_table_length |
異常表包含4個欄位,依次是:u2型別的start_pc、end_pc、handler_pc、catch_type。如果當位元組碼在start_pc行到end_pc之間出現型別為catch_type(指向CONSTANT_Class_info的索引)或者其子型別的異常,則轉到handler_pc行進行處理 |
u2 |
attributes_count |
1 |
屬性數量 |
attribute_info |
attributes |
attributes_count |
屬性表,同上 |
class檔案是按照上面約定的格式無分割組成的二進位制檔案。通過認為將二進位制轉化成ascii碼,然後切分成人為可理解的描述行成本太大,我們只需要理解位元組碼的結構原理,Oracle公司已經幫我們準備好一個專門用於分析Class檔案位元組碼的工具javap。通過使用javap工具的-verbose引數輸出人為可以理解的文字描述。
位元組碼指令
java虛擬機器的指令由一個位元組長度u1、代表著某種特定操作含義的數字(稱為操作碼,Opcode)以及跟隨其後的零至多個代表此操作所需引數(稱為運算元,Operands)而構成。java虛擬機器直譯器執行模型的虛擬碼如下:
do{
自動計算PC暫存器的值加1;
根據PC暫存器的指示位置,從位元組碼流中取出操作碼;
if(位元組碼存在運算元) 從位元組碼流中取出運算元;
執行操作碼所定義的操作;
} while(位元組碼流長度>0)
對於大部分與資料型別相關的位元組碼指令,它們的操作碼助記符中都有特殊的字元來表名專門為哪種資料型別服務:i代表對int型別的資料操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。下表列舉了Java虛擬機器所支援的與資料型別相關的位元組碼指令,通過使用資料型別列所代表的特殊字元替換opcode列中的指令模板中的T,得到一個具體的位元組碼指令。
opcode |
byte |
short |
int |
long |
float |
double |
char |
reference |
描述 |
---|
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 |
根據棧裡內容來把name陣列的第一項的值推至棧頂 |
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 |
條件分支,其中OP可以是eq、ne、gt、null等 |
Treturn |
|
|
ireturn |
lreturn |
freturn |
dreturn |
|
areturn |
返回指令 |
其他指令說明
指令 |
說明 |
---|
pop、pop2 |
將運算元棧的棧頂一個或兩個元素出棧 |
dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2 |
複製棧頂一個或兩個數值並將複製值或雙份的複製值重新壓入棧頂 |
swap |
將棧最頂端兩個數值互換 |
goto、goto_w、jsr、jsr_w、ret |
無條件轉移指令 |
tableswitch、lookupswitch |
複合條件分支轉移指令 |
invokevirtual |
呼叫物件的例項方法,根據物件的實際型別進行分派(虛方法分派) |
invokeinterface |
呼叫介面方法,它會在執行時搜尋一個實現了這個介面方法的物件,找出適合的方法進行呼叫 |
invokestatic |
呼叫類的靜態方法 |
invokedynamic |
執行時動態解析出呼叫點限定符所引用的方法,並執行該方法 |
monitorenter、monitorexit |
synchronized關鍵字修飾語句塊時對應的同步指令 |