這篇文章主要内容來自"深入java虛拟機",剛畢業那會太急功近利,這塊離業務代碼太遠就沒細看。這次花點時間整理一下,加深對位元組碼的認識。
類檔案結構
“一次編寫,到處運作”表達的是“與平台無關”。如何做到“與平台無關”?Sun公司以及其他虛拟機提供商釋出了許多可以運作在各種平台上的虛拟機,這些虛拟機都可以載入和執行同一種平台無關的位元組碼(class檔案)。
Java虛拟機的野心遠不止做到“與平台無關”,Java虛拟機還做到“語言無關性”。Java虛拟機支援其他語言的代碼通過對應語言的編譯器,編譯成位元組碼,進而運作在Java虛拟機之上。
Class檔案是一組以8位位元組為基礎單元的二進制流,各個資料項目嚴格按照順序緊湊地排列在Class檔案之中,中間沒有添加任何分隔符。Class檔案格式采用一種類似于C語言結構體的僞結構來存儲資料,這種僞結構隻有兩種資料類型:無符号數 和 表。
表1:Class檔案格式定義
名稱 | 類型 | 數量 | 描述 |
---|---|---|---|
magic | u4 | 1 | class檔案的識别碼,固定,為“0xCAFFBABE” |
minor_version | u2 | 次版本号,JDK1.7_u91,其中91就是次版本号 | |
major_version | 主版本号,JDK1.7對應值為51,JDK1.8對應值為52,依次類推。虛拟機拒絕執行超過其版本号的Class檔案 | ||
constant_pool_count | 常量池中常量的數量,從1開始計數 | ||
constant_pool | cp_info | 占用Class檔案空間最大的資料項。常量池中存放兩大常量:字面量(java中的常量,如文本字元串、聲明為final的常量值等)和符号引用(類以及接口的全限定名、字段的名稱和描述符、方法的名稱和描述符)。cp_info有14種類型結構,通過開頭的u1類型的标志位來表示,每一種類型的具體格式見 表2:常量池結構表 | |
access_flags | 通路辨別,用于識别class還是接口、是否定義為public、是否定義為abstract、是否被聲明為final等,u2總共16位,每一位可以代表一類資訊是否。具體就不闡述了 | ||
this_class | 類索引,一個指向常量類型為CONSTANT_UTF8_info的全限定名字元串 | ||
super_class | 父類索引,一個指向常量類型為CONSTANT_UTF8_info的全限定名字元串 | ||
interfaces_count | 繼承接口數量 | ||
interfaces | 接口索引,一個指向常量類型為CONSTANT_UTF8_info的全限定名字元串 | ||
fields_count | 字段數量,包括類級變量以及執行個體級數量,但不包括方法内部的局部變量 | ||
fields | field_info | 字段表結構包括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 | 方法的數量 | ||
methods | method_info | 方法的表結構和字段的描述是完全一緻的,方法裡的java代碼,經過編譯器編譯成位元組碼指令後,存放在方法屬性表集合中的一個名為“Code”的屬性裡面。屬性的描述見下行 | |
attributes_count | 屬性的數量 | ||
attributes | attribute_info | arrtributes_count | 在Class檔案、字段表、方法表都可以攜帶自己的屬性表集合。目前虛拟機能識别的屬性類型有21中,見 表3:虛拟機規範預定義的屬性。屬性表結構包含attribute_name_index(屬性類型名稱,指向常量池的索引)、attribute_length(屬性長度,為屬性表總長度-6)、info(屬性資訊,各屬性類型自定義的屬性結構) |
表2:常量池結構表
标志 | ||
---|---|---|
CONSTANT_Utf8_info | 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 | 方法的局部變量描述,描述棧幀中局部變量表中局部變量表中的變量與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 | 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屬性表的結構
attribute_name_index | 一個指向CONSTANT_Utf8_info型常量的索引,固定值為“Code” | ||
attribute_length | 屬性值的長度,整個屬性表長度減去6位元組 | ||
max_stack | 代表操作數棧深度的最大值 | ||
max_locals | 代表局部變量表所需的存儲空間,機關slot(slot是虛拟機為局部變量配置設定記憶體所使用的最小機關,4位元組) | ||
code_length | 位元組碼指令的個數 | ||
u1 | code | 用于存儲位元組碼指令的一系列位元組流,每個位元組碼指令是一個u1類型的單位元組 | |
exception_table_length | 異常表長度 | ||
exception_info | exception_table | 異常表包含4個字段,依次是:u2類型的start_pc、end_pc、handler_pc、catch_type。如果當位元組碼在start_pc行到end_pc之間出現類型為catch_type(指向CONSTANT_Class_info的索引)或者其子類型的異常,則轉到handler_pc行進行處理 | |
屬性數量 | |||
屬性表,同上 |
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關鍵字修飾語句塊時對應的同步指令 |