天天看點

位元組碼

這篇文章主要内容來自"深入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關鍵字修飾語句塊時對應的同步指令