《深入理解java虛擬機(jī)》-類文件結(jié)構(gòu)

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)、方法名稱和描述符。
常量池中的14種常量項(xiàng)的結(jié)構(gòu)總表
使用javap命令輸出常量表

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

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

字段表

字段表集合中不會(huì)列出從超類或者父接口中繼承而來的字段

  • access_flags字段標(biāo)識(shí)符,public、private、protected、static、final、volatile、transient等
  • name_index:字段的簡單名稱
  • descriptor_index:字段或方法的描述符
  • attributes_count:屬性數(shù)量
  • attributes#屬性表
字段表結(jié)構(gòu)

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

字段訪問標(biāo)志

方法表

方法表結(jié)構(gòu)
方法訪問標(biāo)志

屬性表

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

虛擬機(jī)規(guī)范預(yù)定義的屬性
1. Code屬性

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

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
異常表結(jié)構(gòu)
2. Exceptions屬性

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

Exceptions屬性結(jié)構(gòu)
  • 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)

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

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ì)丟失

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

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

Local_variable_info項(xiàng)目結(jié)構(gòu)
  • start_pc:這個(gè)局部變量的生命周期開始的字節(jié)碼偏移量
  • length:作用范圍覆蓋的長度
  • name_index:局部變量名稱
  • descriptor_index:局部變量的描述符
  • index:局部變量在棧幀局部變量表中Slot的位置
5. SourceFile屬性

SourceFile屬性用于記錄生成這個(gè)Class文件的源碼文件名稱,也不是必需的,可以不生成。不生成時(shí),拋出異常堆棧中將不會(huì)顯示出錯(cuò)代碼所屬的文件名

SourceFile屬性結(jié)構(gòu)
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_methods屬性的結(jié)構(gòu)

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

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兩條指令支持。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容