深刻了解Java虛擬機(3)類文件結構

 

 虛擬機執行子系統

1、類文件結構

 

1.魔數和class版本

  1.magic-魔數:0xCAFEBABE;4字節java

  2.minor_version:次版本,丶以後的數字;2字節數組

  3.major_version:主版本,丶以前的數字;2字節安全

2.常量池

  1.constant_pool_count:常量池常量數量(= 此值 - 1):2字節數據結構

    因爲常量池中常量的數量是不固定的,因此在常量池的入口須要放置一項u2類型的數據,表明常量池容量計數值。併發

  2.constant_pool:常量,第一位爲類型位,以後的就是按照各自常量的定義:n字節ide

    

   

   

   

3.訪問標識符

   1.access_flags:訪問標識this

     這個標誌用於識別一些類或者接口層次的訪問信息,包括:這個Class是類仍是接口;是否認義爲public類型;是否認義爲abstract類型;若是是類的話,是否被聲明爲final等。spa

   

    如:0x0001 0x0020說明是一個公共的類3d

4.類索引、父類索引、接口索引:Class文件中由這三項數據來肯定這個類的繼承關係

  1.this_class:類索引:2字節指針

    類索引用於肯定這個類的全限定名

  2.super_class:父類索引:2字節

    父類索引用於肯定這個類的父類的全限定名。因爲Java語言不容許多重繼承,因此父類索引只有一個,除了java.lang.Object以外,全部的Java類都有父類,所以除了java.lang.Object外,全部Java類的父類索引都不爲0

  3.interfaces:接口索引:2字節數組   

    接口索引集合就用來描述這個類實現了哪些接口,這些被實現的接口將按implements語句(若是這個類自己是一個接口,則應當是extends語句)後的接口順序從左到右排列在接口索引集合中

    接口索引開頭爲數量:2字節

  查找

    類索引、父類索引和接口索引集合都按順序排列在訪問標誌以後,類索引和父類索引用兩個u2類型的索引值表示,它們各自指向一個類型爲CONSTANT_Class_info的類描述符常量,經過CONSTANT_Class_info類型的常量中的索引值能夠找到定義在CONSTANT_Utf8_info類型的常量中的全限定名字符串

    

5.字段表集合 

能夠包括的信息有:

  字段的做用域(public、private、protected修飾 符)、是實例變量仍是類變量(static修飾符)、可變性(final)、併發可見性(volatile修飾 符,是否強制從主內存讀寫)、能否被序列化(transient修飾符)、字段數據類型(基本類型、對象、數組)、字段名稱。上述這些信息中,各個修飾符都是布爾值,要麼有某個修飾符,要麼沒有,很適合使用標誌位來表示。

  字段名、字段數據類型,這些都是沒法固定的,只能引用常量池中的常量來描述

   

  1.access_flags:字段訪問標識:u2

    

  2.name_index:字段簡單名稱:u2

    引用常量池常量

  3.descriptor_index:方法描敘符:u2

    描敘字段:字段類型

    描敘方法:(參數列表)描敘符

    引用常量池常量

     

 

 6.方法表集合

  

  

 

7.class、字段、方法等的屬性表

  預約義的有21種,每種都有本身的結構

  1.Code屬性

    

 

    attribute_name_index是一項指向CONSTANT_Utf8_info型常量的索引,常量值固定爲「Code」,它表明了該屬性的屬性名稱

    attribute_length指示了屬性值的長,因此屬性值的長度固定爲整個屬性表長度減去6個字節。

    max_stack表明了操做數棧(Operand Stacks)深度的最大值。在方法執行的任意時刻,操做數棧都不會超過這個深度。虛擬機運行的時候須要根據這個值來分配棧幀(StackFrame)中的操做棧深度。

    max_locals表明了局部變量表所需的存儲空間。

    code_length和code用來存儲Java源程序編譯後生成的字節碼指令。

    code:字節碼

    exception:異常表

      

 

      若是當字節碼在第start_pc行[1]到第end_pc行之間(不含第end_pc行)出現了類型爲catch_type或者其子類的異常(catch_type爲指向一個CONSTANT_Class_info型常量的索引)

    則轉到第handler_pc行繼續處理。當catch_type的值爲0時,表明任意異常狀況都須要轉向到handler_pc處進行處理

//Java源碼
public int inc(){
    int x;
    try{
        x=1return x;
    }catch(Exception e){
        x=2return x;
    }finally{
        x=3;
    }
}/

/編譯後的ByteCode字節碼及異常表
public int inc();
Code:
Stack=1,Locals=5,Args_size=1
0:iconst_1//try塊中的x=1
1:istore_1
2:iload_1//保存x到returnValue中,此時x=1
3:istore 4
5:iconst_3//finaly塊中的x=3
6:istore_1
7:iload 4//將returnValue中的值放到棧頂,準備給ireturn返回
9:ireturn
10:astore_2//給catch中定義的Exception e賦值,存儲在Slot 2中
11:iconst_2//catch塊中的x=2
12:istore_1
13:iload_1//保存x到returnValue中,此時x=2
14:istore 4
16:iconst_3//finaly塊中的x=3
17:istore_1
18:iload 4//將returnValue中的值放到棧頂,準備給ireturn返回
20:ireturn
21:astore_3//若是出現了不屬於java.lang.Exception及其子類的異常纔會走到這裏
22:iconst_3//finaly塊中的x=3
23:istore_1
24:aload_3//將異常放置到棧頂,並拋出
25:athrow
Exception table:
from to target type
0 5 10 Class java/lang/Exception
0 5 21 any
10 16 21 any            

 

      異常表其實是Java代碼的一部分,編譯器使用異常表而不是簡單的跳轉命令來實現Java異常及finally處理機制

  2.Exception屬性

    方法的異常

    

    number_of_exceptions:表示方法可能拋出number_of_exceptions種受查異常

    exception_index_table:一個指向常量池中CONSTANT_Class_info型常量的索引,表明了該受查異常的類型

  3.LineNumberTable屬性

    LineNumberTable:描述Java源碼行號與字節碼行號(字節碼的偏移量)之間的對應關係它並非運行時必需的屬性,但默認會生成到Class文件之中

    能夠在Javac中分別使用-g:none或-g:lines選項來取消或要求生成這項信息。若是選擇不生成LineNumberTable屬性

    對程序運行產生的最主要的影響就是當拋出異常時,堆棧中將不會顯示出錯的行號,而且在調試程序的時候,也沒法按照源碼行來設置斷點

    

      line_number_table:數量爲line_number_table_length、類型爲line_number_info的集合,

      line_number_info:表包括了start_pc和line_number兩個u2類型的數據項,前者是字節碼行號,後者是Java源碼行號

  4.LocalVariableTable屬性

    LocalVariableTable:描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關係,它也不是運行時必需的屬性

    但默認會生成到Class文件之中,能夠在Javac中分別使用-g:none或-g:vars選項來取消或要求生成這項信息。

    若是沒有生成這項屬性,最大的影響就是當其餘人引用這個方法時,全部的參數名稱都將會丟失,IDE將會使用諸如arg0、arg1之類的佔位符代替原有的參數名,這對程序運行沒有影響,可是會對代碼編寫帶來較大不便,並且在調試期間沒法根據參數名稱從上下文中得到參數值

    

    local_variable_info:表明了一個棧幀與源碼中的局部變量的關聯

    

    

    start_pc和length:分別表明了這個局部變量的生命週期開始的字節碼偏移量及其做用範圍覆蓋的長度,二者結合起來就是這個局部變量在字節碼之中的做用域範圍。

    name_index和descriptor_index:指向常量池中CONSTANT_Utf8_info型常量的索引,分別表明了局部變量的名稱以及這個局部變量的描述符。

    index:這個局部變量在棧幀局部變量表中Slot的位置。當這個變量數據類型是64位類型時(double和long),它佔用的Slot爲index和index+1兩個

    泛型的支持屬性

      在JDK 1.5引入泛型以後,LocalVariableTable屬性增長了一個「姐妹屬性」:LocalVariableTypeTable

      這個新增的屬性結構與LocalVariableTable很是類似,僅僅是把記錄的字段描述符的descriptor_index替換成了字段的特徵簽名(Signature),

      對於非泛型類型來講,描述符和特徵簽名能描述的信息是基本一致的,可是泛型引入以後,因爲描述符中泛型的參數化類型被擦除掉

    描述符就不能準確地描述泛型類型了,所以出現了LocalVariableTypeTable。

  5.SourceFile屬性

    SourceFile屬性用於記錄生成這個Class文件的源碼文件名稱。這個屬性也是可選的,能夠分別使用Javac的-g:none或-g:source選項來關閉或要求生成這項信息。

    在Java中,對於大多數的類來講,類名和文件名是一致的,可是有一些特殊狀況(如內部類)例外。若是不生成這項屬性,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名

    

    sourcefile_index:指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源碼文件的文件名

  6.ConstantValue屬性

    ConstantValue屬性的做用是通知虛擬機自動爲靜態變量賦值。

    只有被static關鍵字修飾的變量(類變量)纔可使用這項屬性。

    對於非static類型的變量(也就是實例變量)的賦值是在實例構造器<init>方法中進行的;

    而對於類變量,則有兩種方式能夠選擇:

      若是同時使用final和static來修飾一個變量,而且這個變量的數據類型是基本類型或者java.lang.String的話,就生成ConstantValue屬性來進行初始化

      若是這個變量沒有被final修飾,或者並不是基本類型及字符串,則將會選擇在<clinit>方法中進行初始化

    

    ConstantValue屬性是一個定長屬性

    attribute_length:數據項值必須固定爲2。

    constantvalue_index:數據項表明了常量池中一個字面量常量的引用

      根據字段類型的不一樣,字面量能夠是CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info、CONSTANT_String_info常量中的一種

   7.InnerClasses屬性

    InnerClasses屬性用於記錄內部類與宿主類之間的關聯。若是一個類中定義了內部類,那編譯器將會爲它以及它所包含的內部類生成InnerClasses屬性

    

    number_of_classes:表明須要記錄多少個內部類信息,每個內部類的信息都由一個inner_classes_info表進行描述

    

    inner_class_info_index和outer_class_info_index:指向常量池中CONSTANT_Class_info型常量的索引,分別表明了內部類和宿主類的符號引用。

    inner_name_index:指向常量池中CONSTANT_Utf8_info型常量的索引,表明這個內部類的名稱,若是是匿名內部類,那麼這項值爲0。

    inner_class_access_flags:內部類的訪問標誌,相似於類的access_flags

    

 

   8.Deprecated及Synthetic屬性

    Deprecated屬性用於表示某個類、字段或者方法,已經被程序做者定爲再也不推薦使用,它能夠經過在代碼中使用@deprecated註釋進行設置。

    Synthetic屬性表明此字段或者方法並非由Java源碼直接產生的,而是由編譯器自行添加的,

   9.StackMapTable屬性

    字節碼驗證

  10.Signature屬性

     泛型被擦出後,獲取泛型信息

  11.BootstrapMethods屬性

    BootstrapMethods屬性在JDK 1.7發佈後增長到了Class文件規範之中,它是一個複雜的變長屬性,位於類文件的屬性表中。這個屬性用於保存invokedynamic指令引用的引導方法限定符。

 

 8.字節碼指令簡介

  1.加載和存儲指令

    加載和存儲指令用於將數據在棧幀中的局部變量表和操做數棧(見第2章關於內存區域的介紹)之間來回傳輸,這類指令包括以下內容。  

    將一個局部變量加載到操做棧:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>。

    將一個數值從操做數棧存儲到局部變量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>。

    將一個常量加載到操做數棧:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m一、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>。

    擴充局部變量表的訪問索引的指令:wide。

  2.運算指令

    加法指令: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。

   3.類型轉換指令

    Java虛擬機直接支持(即轉換時無需顯式的轉換指令)如下數值類型的寬化類型轉換(Widening Numeric Conversions,即小範圍類型向大範圍類型的安全轉換):

      int類型到long、float或者double類型。

      long類型到float、double類型。

      float類型到double類型。
    處理窄化類型轉換(Narrowing Numeric Conversions)

      這些轉換指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。

   4.對象訪問與建立

    建立類實例的指令:new。

    建立數組的指令:newarray、anewarray、multianewarray。

    訪問類字段(static字段,或者稱爲類變量)和實例字段(非static字段,或者稱爲實例變量)的指令:getfield、putfield、getstatic、putstatic。

    把一個數組元素加載到操做數棧的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。

    將一個操做數棧的值存儲到數組元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore。

    取數組長度的指令:arraylength。

    檢查類實例類型的指令:instanceof、checkcast。

  5.操做數棧管理指令

    如同操做一個普通數據結構中的堆棧那樣,Java虛擬機提供了一些用於直接操做操做數棧的指令,

    包括:

      將操做數棧的棧頂一個或兩個元素出棧:pop、pop2。

      複製棧頂一個或兩個數值並將複製值或雙份的複製值從新壓入棧頂:dup、dup二、dup_x一、dup2_x一、dup_x二、dup2_x2。

      將棧最頂端的兩個數值互換:swap

  6.控制轉移指令

    條件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq和if_acmpne。

    複合條件分支:tableswitch、lookupswitch。

    無條件分支:goto、goto_w、jsr、jsr_w、ret。

  7.方法調用和返回

    invokevirtual指令用於調用對象的實例方法,根據對象的實際類型進行分派(虛方法分派),這也是Java語言中最多見的方法分派方式。

    invokeinterface指令用於調用接口方法,它會在運行時搜索一個實現了這個接口方法的對象,找出適合的方法進行調用。

    invokespecial指令用於調用一些須要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法。

    invokestatic指令用於調用類方法(static方法)。

    invokedynamic指令用於在運行時動態解析出調用點限定符所引用的方法,並執行該方法,前面4條調用指令的分派邏輯都固化在Java虛擬機內部,

    invokedynamic指令的分派邏輯是由用戶所設定的引導方法決定的。方法調用指令與數據類型無關,而方法返回指令是根據返回值的類型區分的,包括ireturn(當返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn和areturn,另外還有一條return指令供聲明爲void的方法、實例初始化方法以及類和接口的類初始化方法使用

  8.異常處理指令

    在Java程序中顯式拋出異常的操做(throw語句)都由athrow指令來實現

    除了用throw語句顯式拋出異常狀況以外,Java虛擬機規範還規定了許多運行時異常會在其餘Java虛擬機指令檢測到異常情況時自動拋出。

    例如,在前面介紹的整數運算中,當除數爲零時,虛擬機會在idiv或ldiv指令中拋出ArithmeticException異常。

    而在Java虛擬機中,處理異常(catch語句)不是由字節碼指令來實現的(好久以前曾經使用jsr和ret指令來實現,如今已經不用了),而是採用異常表來完成的。

  9.同步指令

    同步一段指令集序列一般是由Java語言中的synchronized語句塊來表示的

    Java虛擬機的指令集中有monitorenter和monitorexit兩條指令來支持synchronized關鍵字的語義

void onlyMe(Foo f){
    synchronized(f){
    doSomething();
    }
}

// 指令碼
Method void onlyMe(Foo)
0 aload_1//將對象f入棧
1 dup//複製棧頂元素(即f的引用)
2 astore_2//將棧頂元素存儲到局部變量表Slot 2中
3 monitorenter//以棧頂元素(即f)做爲鎖,開始同步
4 aload_0//將局部變量Slot 0(即this指針)的元素入棧
5 invokevirtual#5//調用doSomething()方法
8 aload_2//將局部變量Slow 2的元素(即f)入棧
9 monitorexit//退出同步
10 goto 18//方法正常結束,跳轉到18返回
13 astore_3//從這步開始是異常路徑,見下面異常表的Taget 13
14 aload_2//將局部變量Slow 2的元素(即f)入棧
15 monitorexit//退出同步
16 aload_3//將局部變量Slow 3的元素(即異常對象)入棧
17 athrow//把異常對象從新拋出給onlyMe()方法的調用者
18 return//方法正常返回
Exception table:
FromTo Target Type
4 10 13 any
13 16 13 any        
相關文章
相關標籤/搜索