Java在剛剛誕生之初曾經(jīng)提出過一個(gè)非常著名的口號(hào):“一次編譯,導(dǎo)出執(zhí)行”,將java文件編譯為class文件,然后由JVM來處理平臺(tái)的差異性,這個(gè)特性也使其他的語言能夠在JVM上運(yùn)行。
Class類文件的結(jié)構(gòu)
| 占用大小 | 字段描述 | 數(shù)量 |
|---|---|---|
| 4bit | magic:魔數(shù),用于標(biāo)識(shí)文件類型,對(duì)于java來說是0xCAFEBABE | 1 |
| 2bit | minor_version:次版本號(hào) | 1 |
| 2bit | major_version:主版本號(hào) | 1 |
| 2bit | constant_pool_count:常量池大小,從1開始而不是0。當(dāng)這個(gè)值為0時(shí),表示后面沒有常量 | 1 |
| 不定 | constant_pool:#常量池 | constant_pool_count-1 |
| 2bit | access_flags:訪問標(biāo)志,標(biāo)識(shí)這個(gè)class是類還是接口、public、abstract、final等 | 1 |
| 2bit | this_class:類索引 #類索引查找全限定名的過程 | 1 |
| 2bit | super_class:父類索引 | 1 |
| 2bit | interfaces_count:接口計(jì)數(shù)器 | 1 |
| 每個(gè)2bit | interfaces:接口索引集合 | interfaces_count |
| 2bit | fields_count:字段的數(shù)量 | 1 |
| 不定 | fields:#字段表 | fields_count |
| 2bit | methods_count:方法數(shù)量 | 1 |
| 不定 | methods:#方法表 | methods_count |
| 2bit | attributes_count:屬性數(shù)量 | 1 |
| 不定 | attrbutes:#屬性表 | attributes_count |
常量池
主要存放兩大類常量:
- 字面量(Literal):類似于java的常量
- 符號(hào)引用(Symbolic References):屬于編譯原理方面的概念,主要包括類和接口的權(quán)限定名(Fully Qualified Name)、字段名稱和描述符(Descriptor)、方法名稱和描述符。


類索引查找全限定名的過程

字段表
字段表集合中不會(huì)列出從超類或者父接口中繼承而來的字段
- access_flags:字段標(biāo)識(shí)符,public、private、protected、static、final、volatile、transient等
- name_index:字段的簡單名稱
- descriptor_index:字段或方法的描述符
- attributes_count:屬性數(shù)量
- attributes:#屬性表

字段訪問標(biāo)志:

方法表


屬性表
Class文件、字段表、方法表都可以攜帶自己的屬性表數(shù)據(jù)集合。與Class文件中其他的數(shù)據(jù)項(xiàng)目要求嚴(yán)格不同,屬性表集合的限制相對(duì)寬松。在java虛擬機(jī)規(guī)范1.7版本中定義了21項(xiàng)屬性。

1. Code屬性
方法體內(nèi)java代碼編譯后生成的字節(jié)碼指令存儲(chǔ)在Code屬性內(nèi),Code屬性表的結(jié)構(gòu):

- attribute_name_index:是一項(xiàng)指向CONSTANT_Utf8_info型常量的索引,常量為“Code”
- attribute_length:屬性值的長度
- max_stack:操作數(shù)棧(Operand Stacks)深度的最大值
- max_locals:局部變量表所需的存儲(chǔ)空間
- code_length:字節(jié)碼長度
- code:用于存儲(chǔ)字節(jié)碼指令的一系列字節(jié)流,每個(gè)指令就是一個(gè)1bit的單字節(jié) 一字節(jié) => 指令 => 動(dòng)作 (linux命令行、匯編)
- exception_table_length:異常表長度
-
exception_table:異常表
- start_pc:開始行
- end_pc:結(jié)束行
- handler_pc:處理異常行
- catch_type:當(dāng)catch_type類型或其子類型的異常發(fā)生時(shí),轉(zhuǎn)到handler_pc

2. Exceptions屬性
Exceptions屬性的作用是列舉處方法中可能拋出的受檢異常(Checked Exceptions)

- number_of_exceptions:受檢異常數(shù)
- exception_index_table:是一個(gè)指向CONSTANT_Class_info型常量的索引,代表異常類型
3. LineNumberTable屬性
LineNumberTable屬性用于描述java源碼行號(hào)與字節(jié)碼號(hào)碼之間的對(duì)應(yīng)關(guān)系。它不是運(yùn)行時(shí)必需的屬性,可以不生成。不生成時(shí),拋出異常堆棧中不會(huì)顯示出錯(cuò)的行號(hào)

line_number_table是一個(gè)數(shù)量為line_number_table_length,類型為line_number_info的集合,line_number_info表包括了start_pc和line_number兩個(gè)2bit的數(shù)據(jù)項(xiàng),前者是字節(jié)碼行號(hào),后者是java源碼行號(hào)
4. LocalVariableTable屬性
LocalVariableTable屬性用于描述幀棧中局部變量表中的變量與java源碼中定義的變量之間的關(guān)系,也不是必需的,可以不生成。不生成時(shí),其他人引用這個(gè)方法,所有的參數(shù)名稱都將會(huì)丟失

local_variable_info項(xiàng)目代表了一個(gè)棧幀與源碼中的局部變量的關(guān)聯(lián)

- start_pc:這個(gè)局部變量的生命周期開始的字節(jié)碼偏移量
- length:作用范圍覆蓋的長度
- name_index:局部變量名稱
- descriptor_index:局部變量的描述符
- index:局部變量在棧幀局部變量表中Slot的位置
5. SourceFile屬性
SourceFile屬性用于記錄生成這個(gè)Class文件的源碼文件名稱,也不是必需的,可以不生成。不生成時(shí),拋出異常堆棧中將不會(huì)顯示出錯(cuò)代碼所屬的文件名

6. ConstantValue屬性
ConstantValue屬性的作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值
7. InnerClasses屬性
InnerClasses屬性用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)
8. Deprecated及Synthetic屬性
這兩個(gè)屬性都是布爾屬性。Deprecated代表這個(gè)類、字段或方法已經(jīng)過時(shí),不再推薦使用。Synthetic代表此字段或者方法不是由java源碼直接產(chǎn)生的,而是由編譯器自行添加的。
9. StackMapTable屬性
StackMapTable屬性會(huì)在虛擬機(jī)類加載的字節(jié)碼驗(yàn)證階段被新類型檢查驗(yàn)證器(Type Checker)使用目的在于代替以前比較消耗性能的基于數(shù)據(jù)流分析的類型推導(dǎo)驗(yàn)證器
10. Signature屬性
Signature屬性用于記錄泛型簽名信息
11. BootstrapMethods屬性
BootstrapMethods屬性用于保存invokedynamic指令引用的引導(dǎo)方法限定符

bootstrap_method屬性結(jié)構(gòu)

- bootstrap_method_ref:必須是一個(gè)對(duì)常量池中CONSTANT_MethodHandle_info結(jié)構(gòu)的有效索引
- num_bootstrap_arguments:bootstrap_arguments的數(shù)量
- bootstrap_arguments[]:必須是一個(gè)對(duì)常量池的有效索引
字節(jié)碼指令簡介
java虛擬機(jī)的指令由一個(gè)字節(jié)長度的、代表著某種特定操作含義的數(shù)字(操作碼Opcode)以及跟隨其后的0至多個(gè)代表此操作所需參數(shù)(操作數(shù)Operands)而構(gòu)成。由于java虛擬機(jī)采用面向操作數(shù)棧而不是寄存器的架構(gòu),所以大多數(shù)的指令都不包含操作數(shù),只有一個(gè)操作碼
字節(jié)碼與數(shù)據(jù)類型
- l代表long
- s代表short
- b代表byte
- c代表char
- f代表float
- d代表double
- a代表reference
在java虛擬機(jī)中指令集不是完全獨(dú)立的(Not Orthogonal),即不是每種數(shù)據(jù)類型和每一種操作都有對(duì)應(yīng)的指令,有一些單獨(dú)的指令可以在必要的時(shí)候用在將一些不支持的類型轉(zhuǎn)換為可被支持的類型
加載和存儲(chǔ)命令
加載和存儲(chǔ)指令用于將數(shù)據(jù)在幀棧中的局部變量表和操作數(shù)棧之間來回傳遞
- 將一個(gè)局部變量加載到操作棧:iload、iload_<\n>、lload、lload_<\n>、fload、fload_<\n>、dload、dload_<\n>、aload、aload_<\n>
- 將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:istore、istore_<\n>、lstore、lstore_<\n>、fstore、fstore_<\n>、dstore、dstore_<\n>、astore、astore_<\n>
- 將一個(gè)參數(shù)加載到操作數(shù)棧:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<\i>、lconst_<l>、fconst_<\f>、dconst_<\d>
- 擴(kuò)充局部變量表的訪問索引的指令:wide
上面帶尖括號(hào)的指令實(shí)際上是代表的一組指令,如iload_0、iload_1、iload_2和iload_3。這些指令把操作數(shù)隱含在名稱內(nèi),不需要進(jìn)行取操作數(shù)的動(dòng)作
運(yùn)算指令
運(yùn)算或算術(shù)指令用于對(duì)兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定運(yùn)算,并把結(jié)果重新存入到操作棧頂,可分為整型數(shù)據(jù)和浮點(diǎn)型數(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)換,一般用于實(shí)現(xiàn)用戶代碼中的顯示類型轉(zhuǎn)換操作,或者處理字節(jié)碼指令集中數(shù)據(jù)類型相關(guān)指令無法與數(shù)據(jù)類型一一對(duì)應(yīng)的問題
- 寬化類型轉(zhuǎn)換(Widening Numeric Conversions,即小范圍類型向大范圍類型的安全轉(zhuǎn)換):轉(zhuǎn)換時(shí)無需顯式的轉(zhuǎn)換指令
- 窄化類型轉(zhuǎn)換(Narrowing Numeric Conversions):必需顯式地使用轉(zhuǎn)換指令。i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f,轉(zhuǎn)換時(shí)可能導(dǎo)致數(shù)值的進(jìn)度丟失
對(duì)象創(chuàng)建與訪問指令
- 創(chuàng)建類實(shí)例的指令:new
- 創(chuàng)建數(shù)組的指令:newarray、anewarray、multianewarray
- 訪問類字段和實(shí)例字段的實(shí)例:getfield、putfield、getstatic、putstatic
- 把一個(gè)數(shù)組元素加載到操作數(shù)棧的指令:baload、caload、saload、iaload、laload、faload、daload、aaload
- 將一個(gè)操作數(shù)棧的值存儲(chǔ)到數(shù)組元素中的指令:bastore、castore、sastore、iastore、fasotre、dastore、aastore
- 取數(shù)組長度的指令:arraylength
- 檢查類實(shí)例類型的指令:instanceof、checkcast
操作數(shù)棧管理指令
- 將操作數(shù)棧的棧頂一個(gè)或兩個(gè)元素出棧:pop、pop2
- 復(fù)制棧頂一個(gè)或兩個(gè)數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2
- 將棧最頂端的兩個(gè)數(shù)值互換:swap
控制轉(zhuǎn)移指令
- 條件分支:fieq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmpge、if_acmpeq、if_acmpne
- 復(fù)合條件分支:tableswitch、lookupswitch
- 無條件分支:goto、goto_w、jsr、jsr_w、ret
方法調(diào)用和返回指令
- invokevirtual指令:用于調(diào)用對(duì)象的實(shí)例方法,根據(jù)對(duì)象的實(shí)際類型進(jìn)行分派
- invokeinterface指令:用于調(diào)用接口方法
- invokespecial指令:用于調(diào)用一些需要特殊處理的實(shí)例方法,包括實(shí)例初始化方法、私有方法和父類方法
- invokestatic指令:用于調(diào)用類方法(static方法)
- invokedynamic指令:用于在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,并執(zhí)行該方法
異常處理指令
在java程序中,顯式拋出異常的操作都由athrow指令來實(shí)現(xiàn)。而在java虛擬機(jī)中,處理異常不是由字節(jié)碼指令來實(shí)現(xiàn)的,而是采用異常表來完成的
同步指令
java虛擬機(jī)可以支持方法級(jí)的同步和方法內(nèi)部一段指令序列的同步,這兩種同步結(jié)構(gòu)都是使用管程(Monitor)來支持的。方法級(jí)的同步是隱式的,利用方法表結(jié)構(gòu)中的ACC_SYNCHRONIZED訪問標(biāo)志得知。指令序列的同步是由monitorenter和monitorexit兩條指令支持。