深入理解 Java 虛擬機(四)類文件結構

無關性

計算機雖然只能識別 0 和 1,但將代碼編譯爲本地機器碼已不再是唯一的選擇,越來越多的程序語言選擇離了與操作系統無關和機器指令集無關的、平臺中立的格式作爲代碼編譯後的存儲格式,比如 Java 程序編譯後的結果就是字節碼,可運行於 Java 虛擬機中。

Java 在誕生之初的宣傳口號是“一次編譯,到處運行”(Write Once, Run Anywhere),平臺無關性的基礎就是虛擬機和字節碼存儲格式。

與此同時,語言無關性也越來越受到重視——即 Java 虛擬機不與 Java 語言綁定,它只與 Class 文件相關聯,只要能把 Java、Groovy 等代碼編譯爲 Class 文件即可。

Class 類文件的結構

在關於 Class 文件結構的講解中,將以《Java 虛擬機規範(第 2 版)》(1999 年發佈,對應 JDK 1.4)中的定義爲主線,這部分內容雖然古老,但它所包含的指令、屬性是 Class 文件最重要和最基礎的。

Class 文件是一組以字節爲基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在 Class 文件之中,中間沒有添加任何分隔符,當遇到需要佔用一個字節以上的空間的數據項時,則按照 Big-Endian 的方式分割爲多個字節進行存儲。

Class 文件採用一種類似於 C 語言結構體的僞結構來存儲數據,這種僞結構只有兩種數據類型:無符號數和表。無符號數屬於基本的數據類型,以 u1、u2、u4、u8 分別代碼 1 個字節、2 個字節、4 個字節和 8 個字節的無符號數,無符號數可以用於描述數字、索引引用、數量值或按照 UTF-8 編碼構成的字符串值。表是由多個無符號數或其他表作爲數據項構成的複合數據類型,通常以 “_info” 結尾,整個 Class 文件本質上就是一張表。

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

魔數與 Class 文件的版本

magic 佔 4 個字節,它的唯一作用是確定這個文件是否爲一個能被虛擬機接受的 Class 文件,它的值很有浪費氣息,爲 “CAFEBABA”(咖啡寶貝?)。

緊接着 4 個字節存儲的是 Class 文件的版本號:minor_version、major_version。Java 的版本號是從 45 開始的,每一個主版本號向上加 1,高版本 JDK 能向下兼容以前的 Class 文件,但不能運行以後版本的 Class 文件,即使文件格式未發生變化。比如 JDK 1.1 的版本號爲 45.0 ~ 45.65535,JDK 1.2 則能支持 45.0 ~ 46.65535 的版本號。

下面的內容都將以這段代碼爲基礎進行講解:

package org.fenixsoft.clazz;

public class TestClass {

    private int m;

    public int inc() {
        return m + 1;
    }

}

使用 WinHex 打開:

這裏寫圖片描述

下面以 “圖 1” 代表該圖。

可以看到,minor version 的值爲 0x0000,major version 的值爲 0x0034,即十進制的 52,說明這個是可以被 JDK 1.8 以上版本的虛擬機執行的 Class 文件。

2.2 常量池

再之後是常量池入口,常量池可以理解爲 Class 文件的資源倉庫,是 Class 文件結構中與其它項目關聯最多的數據類型,也是佔用 Class 文件空間最大的數據項目之一。

前兩個字節是 constant_pool_count,觀察上圖發現,constant_pool_count 值爲 0x0013,即十進制的 19,根據類文件結構,可知常量池中有 18 個常量,爲什麼是 constant_pool_count-1 呢?這是因爲第 0 項常量是 Java 虛擬機規範設計者特意空出來的,用於表達“不引用任何一個常量池項目”。

常量池主要存放兩大類常量:字面量和符號引用。字面值指文本字符串、聲明爲 final 的常量等;符號引用則包括以下三類常量:
1) 類和接口的全限定名(org/fenixsoft/clazz/TestClass)
2) 字段的名稱和描述符(Ljava/lang/String)
3) 方法的名稱和描述符

常量池每一項常量都是一個表,且都有如下通用格式:

cp_info {
    u1 tag;
    u1 info[];
}

其中第一個 tag 代表常量的類型:

Constant Type Value
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18

再看圖 1,第一項常量的 tag 爲 0x0A,即十進制的 10,因此該常量類型 CONSTANT_Methodref,結構如下:

CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

class_index 是一個索引,值爲 0x0004,即指向第 4 項常量,同理,name_and_type_index 值爲 0x000F,指向第 15 項常量。

接下來看第二項常量,類型爲 0x09,對比上表,可知該常量類型爲 CONSTANT_Fieldref,它和 CONSTANT_Methodref 擁有一樣的結構:

CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

查看圖 1 可知 class_index 值爲 0x0003,指向第三項常量,name_and_type_index 值爲 0x0010,指向第 16 項常量。

接着看第三項常量,也是第二項常量指向的 class,類型爲 0x07,即 CONSTANT_Class:

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

值爲 0x0011,指向第 17 項常量。

第四項常量類型依然爲 0x07,即 CONSTANT_Class,也就是第一項常量指向的 class。

以此類推,即可分析出後續的常量及其含義,這裏可以藉助 javap 直接得到分析結果:

F:\> javap -verbose TestClass.class
Classfile /TestClass.class
  Last modified 2018-8-6; size 295 bytes
  MD5 checksum 81f2ab948a7a3068839b61a8f91f634b
  Compiled from "TestClass.java"
public class org.fenixsoft.clazz.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#16         // org/fenixsoft/clazz/TestClass.m:I
   #3 = Class              #17            // org/fenixsoft/clazz/TestClass
   #4 = Class              #18            // java/lang/Object
   #5 = Utf8               m
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               ()I
  #13 = Utf8               SourceFile
  #14 = Utf8               TestClass.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = NameAndType        #5:#6          // m:I
  #17 = Utf8               org/fenixsoft/clazz/TestClass
  #18 = Utf8               java/lang/Object
{
  public org.fenixsoft.clazz.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>
":()V
         4: return
      LineNumberTable:
        line 3: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 8: 0
}
SourceFile: "TestClass.java"

可以看到,對於類、方法、字段,它們都有一個指向 CONSTANT_Utf8_info 的索引,即都需要引用 CONSTANT_Utf8_info 型常量來描述名稱,因此 CONSTANT_Utf8_info 型常量的最大長度就是 Java 中方法、字段名的最大長度,而 CONSTANT_Utf8_info 格式如下:

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

CONSTANT_Utf8_info 的長度爲 length,是一個 u2 類型的無符號數,所以,Java 中的方法、字段名的最大長度不能超過 64KB。

下面以 “class_info” 代表該分析結果。

訪問標誌

常量池之後是 access_flags,即訪問標誌,根據 class_info,可知 TestClass 的訪問標誌有:ACC_PUBLIC、ACC_SUPER。各個訪問標誌代表的含義是:

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 是否爲 public
ACC_FINAL 0x0010 是否爲 final
ACC_SUPER 0x0020 是否允許使用 invokespacial 字節碼指令的新語意,JDK 1.2 之後改標誌都必須位真
ACC_INTERFACE 0x0200 是否爲 interface
ACC_ABSTRACT 0x0400 是否爲 abstract
ACC_ABSTRACT 0x1000 標識這個類不是由用戶代碼產生的
ACC_ANNOTATION 0x2000 標識這是一個註解
ACC_ENUM 0x4000 標識這是一個枚舉

類索引、父類索引和接口索引集合

所有的 Java 類都有父類,因此除了 Object 類之外,所有 Java 類的父類索引都不爲 0。

根據 class_info 可知 TestClass 的類索引、父類索引如下:

  #3 = Class  #17// org/fenixsoft/clazz/TestClass
  #4 = Class  #18// java/lang/Object
  ...
  #17 = Utf8   org/fenixsoft/clazz/TestClass
  #18 = Utf8   java/lang/Object

字段表集合

字段(field_info)用於描述接口或類中聲明的變量,包括類中聲明的成員變量以及靜態變量,不包括方法內部聲明的局部變量。字段包含的信息有:public/private、static、final、volatile、transient(是否被序列化) 等修飾符和字段數據類型、字段名稱等信息。各個修飾符都是布爾值,而字段的數據類型、名稱,則引用常量池中的常量來描述。格式如下:

field_info {
   u2 access_flags;
   u2 name_index; // 指向常量池中 Constant_Utf8_info 常量
   u2 descriptor_index;
   u2 attributes_count;
   attribute_info attributes[attributes_count];
}

方法表集合

方法表和字段表的格式一樣:

method_info {
   u2 access_flags;
   u2 name_index;
   u2 descriptor_index;
   u2 attributes_count;
   attribute_info attributes[attributes_count];
}

和字段表不同的是,access_flags 沒有 volatile 和 transient 標誌,但增加了 synchronized、native、strictfp、abstract 等標誌,具體可自行查看 Java VM 規範文檔。

而方法裏的 Java 代碼,經過編譯器編譯成字節碼指令後,存放在屬性表集合中一個名爲 “Code” 的屬性裏面。

屬性表集合

Class 文件、字段表、方法表都可以攜帶自己的屬性表集合,以用於描述某些場景轉悠的信息。

與 Class 文件中其它的數據項目要求嚴格的順序、長度和內容不同,屬性表集合的限制稍微寬鬆了一些,不再要求各個屬性表具有嚴格順序,並且只要不與已有屬性名重複,任何人實現的編譯器都可以向屬性表中寫入自己定義的屬性信息。

虛擬機規範預定義的屬性有:

1) 對於 Java 虛擬機正確解釋類文件至關重要的 5 個屬性:
• ConstantValue
• Code
• StackMapTable
• Exceptions
• BootstrapMethods

2) 對於 Java SE 平臺的類庫正確解釋類文件至關重要的 12 個屬性:
• InnerClasses
• EnclosingMethod
• Synthetic
• Signature
• RuntimeVisibleAnnotations
• RuntimeInvisibleAnnotations
• RuntimeVisibleParameterAnnotations
• RuntimeInvisibleParameterAnnotations
• RuntimeVisibleTypeAnnotations
• RuntimeInvisibleTypeAnnotations
• AnnotationDefault
• MethodParameters

3) 對於解釋類文件並不重要,但作爲工具很有用的 6 個屬性
• SourceFile
• SourceDebugExtension
• LineNumberTable
• LocalVariableTable
• LocalVariableTypeTable
• Deprecated

各個屬性的通用格式如下:

attribute_info {
    u2 attribute_name_index; // 指向常量池中的 Constant_Utf8_info 常量
    u4 attribute_length;
    u1 info[attribute_length];
}

Code 屬性

Code 屬性表定義如下:

Code_attribute {
       u2 attribute_name_index;
       u4 attribute_length;
       u2 max_stack;
       u2 max_locals;
       u4 code_length;
       u1 code[code_length];
       u2 exception_table_length;
       { u2 start_pc;
         u2 end_pc;
         u2 handler_pc;
         u2 catch_type;
       } exception_table[exception_table_length];
       u2 attributes_count;
       attribute_info attributes[attributes_count];
}

max_stack 代表操作數棧深度的最大值。

max_locals 代表局部變量(包括方法參數、顯示異常處理器的參數)所需的存儲空間,單位是 Slot,byte、char、int、short、boolean 等長度不超過 32 位的數據類型佔用 1 個 Slot,double、long 章用 2 個 Slot。Slot 可以重用,比如代碼執行超出了一個局部變量的作用域,這個局部變量所佔的 Slot 就可以被其它局部變量使用,因此 max_locals 的值小於等於 Slot 的和。

code_length 代表字節碼長度,code 是用於存儲字節碼指令的一系列字節流,每個字節碼指令都是一個 u1 類型的單字節。值得注意的是,雖然 code 類型爲 u4,但虛擬機規範明確限制了一個方法不允許超過 65536 條字節碼指令,如果超過這個限制,javac 編譯器會拒絕編譯。一般不太可能超過限制,但在某些特殊情況下,比如編譯一個很複雜的 jsp 文件時,因爲某些 jsp 編譯器可能會把 jsp 內容和頁面輸出的信息歸併到一個方法中,而導致方法超長編譯失敗。

Code 屬性是 Class 文件中最重要的一個屬性,如果把 Java 程序分爲代碼和元數據,那麼 Code Code 屬於就是用於描述代碼的,所有其它數據項目都用於描述元數據。

爲了方便分析,這裏把 TestClass.class 使用 javap 工具分析後的部分結果再貼一遍:

{
  public org.fenixsoft.clazz.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>
":()V
         4: return
      LineNumberTable:
        line 3: 0

  public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field m:I
         4: iconst_1
         5: iadd
         6: ireturn
      LineNumberTable:
        line 8: 0
}

可以注意到,TestClass 的 和 inc() 方法雖然都沒有參數,但 args_size 都爲 1,這是因爲 java 編譯器編譯的時候會把 “this” 作爲普通參數自動傳入到方法中,因此,局部變量表會預留第一個 Slot 位來存放對象實例的引用,方法參數值從 1 開始計算。但如果是 static 方法,args 就不是 1 而是 0 了。

根據 Code_attribute,code 之後是 exception_table:

u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];

這些字段的含義是:當字節碼在第 start_pc 行到 end_pc 行之間出現了類型爲 catch_type 或者其子類的異常,則轉到 handler_pc 行繼續執行。

比如下面這段代碼:

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

編譯後字節碼如下:

public int inc();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=5, args_size=1
         0: iconst_1 // trye 塊中的 1
         1: istore_1
         2: iload_1 // 保存 x 到 returnValue 中,此時 x=1
         3: istore_2
         4: iconst_3 // finally 塊中的 3
         5: istore_1
         6: iload_2 // 將 returnValue 中的值放到站定,準備給 ireturn 返回
         7: ireturn
         8: astore_2 // 給 catch 中定義的 Exception e 複製,存儲在 Slot 2 中
         9: iconst_2 // catch 塊中的 x=2
        10: istore_1
        11: iload_1 // 保存 x 到 returnValue 中,此時 x=2
        12: istore_3
        13: iconst_3 // finally 塊中的 3
        14: istore_1
        15: iload_3 // 將 returnValue 中的值放到站定,準備給 ireturn 返回
        16: ireturn
        17: astore        4 // 如果出現了不屬於 java.land.Exception 及其子類的異常纔會走到這裏
        19: iconst_3  // finally 塊中的 x=3
        20: istore_1
        21: aload         4 // 將異常放置到棧頂,並輸出
        23: athrow
      Exception table:
         from    to  target type
             0     4     8   Class java/lang/Exception
             0     4    17   any
             8    13    17   any
            17    19    17   any

分析可知,代碼的執行結果是:如果沒有出現異常,則返回 1;出現了 Exception 異常,則返回 2;出現了 Exception 以外的異常,方法非正常退出,沒有返回值。

Exceptions 屬性

Exceptions 屬性的作用是列舉出方法中可能拋出的受檢查異常,也就是方法描述時在 throws 關鍵字後面列舉的異常。

LineNumberTable 屬性

LineNumberTable 用於描述 Java 源碼與字節碼行號之間的對應關係。

LineNumberTable 不是運行時必需的屬性,如果選擇不生成該屬性,則在程序拋出異常時,堆棧中將無法顯示出錯的行號,且無法設置斷點調試程序。

LocalVariableTable 屬性

用於描述棧幀中局部變量表中的變量與 Java 源碼中定義的變量之間的關係。

LocalVariableTable 同樣不是運行時必須的屬性,如果選擇不生成該屬性,最大的影響是當別人引用這個方法時,所有的參數名稱都將丟失,IDE 會使用 arg0、arg1 之類的名稱代替原有的參數名,且在調試期間無法根據參數名稱獲取參數值。

在 JDK 1.5 引入泛型之後,新增了一個屬性爲 LocalVariableTypeTable,因爲 Java 中的泛型是僞泛型,參數化類型會被擦除掉,描述符無法準確地描述泛型類型,因此出現了 LocalVariableTypeTable。

SourceFile 屬性

SourceFile 屬性用於記錄生成這個 Class 文件的源碼文件的名稱,類名和文件名可能不一致,比如內部類。

這個屬性也是可選的,如果選擇不生成,當拋出異常時,堆棧中不會顯示出錯代碼所屬的文件名。

ConstantValue 屬性

ConstantValue 用於通知虛擬機自動爲靜態變量賦值。

對於非 static 變量的賦值是在構造方法 < init > 中進行的;對於 static 變量,則可以在類構造器 < clinit > 方法中或者使用 ConstantValue 屬性。目前 Sun Javac 編譯器的選擇是:如果同時使用 final 和 static 修飾,並且是基本數據類型或者 String 類型,則生成 ConstantValue 屬性進行初始化;否則在 < clinit > 方法中進行初始化。

InnerClass 屬性

InnerClass 屬性用於記錄內部類與宿主類之間的關聯。

Deprecated 及 Synthetic 屬性

Deprecated 屬性用於標識某個類、字段或方法已被程序作者定爲不再推薦使用。

Synthetic 屬性代表此字段活着方法不是 Java 源碼直接產生的,而是編譯器自行添加的。

StackMapTable 屬性

StackMapTable 是 JDK 1.6 新增的,這個屬性會在虛擬機類加載的字節碼驗證階段被新類型檢查驗證器使用,目的在於替代以前比較消耗性能的基於數據流分析的類型推導驗證器。

一個方法的 Code 屬性最多只能有一個 StackMapTable 屬性,如果方法中的 Code 屬性沒有附帶 StackMapTable 屬性,則意味着它帶有一個隱式的 StackMapTable 屬性。

Signature 屬性

Signature 是 JDK 1.5 之後新增的,可以出現在類、屬性和方法表結構的屬性表中,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量或參數化類型,則 Signature 屬性會爲它記錄泛型簽名信息。

之所以要專門使用一個屬性去記錄泛型類型,是因爲 Java 語言的泛型採用的是擦除法實現的僞泛型,在字節碼(Code 屬性)中,泛型信息編譯之後都會被擦除掉。使用擦除法的好處是實現簡單、節省內存空間,壞處是無法像 C++/C# 那樣將泛型與用戶定義的普通類型同等對待,例如運行期就無法獲取泛型信息。Signature 就是爲了解決這個缺陷而設的,現在 Java 的反射 API 能夠獲取泛型類型,最終的數據來源就是這個屬性。

BootstrapMethod 屬性

BootstrapMethod 是 JDK 1.7 新增的,位於類文件的屬性表中,如果某個類文件結構的常量池中曾經出現過 CONSTANT_InvokeDynamic_info 類型的常量,則這個類文件的屬性表中必須存在一個明確的 BootstrapMethod 屬性,另外,即使出現過多個 CONSTANT_InvokeDynamic_info 常量,最多也只會有一個 BootstrapMethod。

字節碼指令

Java 虛擬機的指令由一個字節長度的、代表着某中特定操作含義的數字(Opcode,稱爲操作碼)以及跟隨其後的零個或多個代表此操作所需的參數(Operands,稱爲操作數)組成。由於 Java 虛擬機採用面向操作數棧而不是寄存器的架構,因此大多數指令都不包含操作數,只有一個操作碼。

字節碼指令是一種具有鮮明特點,優劣勢都很突出的指令集架構。由於限制了 Java 虛擬機操作碼的長度爲一個字節,這意味着指令集的操作碼總數不可能超過 256 條;而對於那些超過一個字節的數據,虛擬機需要在運行時重建出具體數據的結構,這在某種程度上會導致解釋執行字節碼時會損耗一些性能。但優勢也很明顯,放棄了操作數對齊,意味着可以省略很多填充和中間符號。這種追求小數據量、高效率傳輸的設計是 Java 誕生之初面向網絡、智能家電的技術背景所決定的,並一直沿用至今。

如果不考慮異常處理,那麼 Java 虛擬機的解釋器可以使用下面這個僞代碼作爲最基本的執行模型來理解:

do {
    自動計算 PC 寄存器的值加 1
    根據 PC 寄存器的指示位置,從字節碼流中取出操作碼
    if (字節碼存在操作數) 從字節碼流中取出操作數
    執行操作碼所定義的操作
} while(字節碼流長度 > 0)

字節碼與數據類型

大多數的指令都包含了其操作所對應的數據類型,比如 iload、lload、fload、dload、aload 等,分別對應 int、long、float、double、reference。同時,大部分的指令都不支持 byte、char 和 short,甚至沒有任何指令支持 boolean,編譯器會在編譯期或運行期將 byte 和 short 數據類型帶符號擴展爲 int 類型數據,將 boolean 和 char 零爲擴展(無符號)爲 int 類型數據。

加載和存儲指令

加載和存儲指令用於將數據在棧幀中的局部變量和操作數棧之間來回傳輸,包括:

1) 將一個局部變量加載到操作棧:iload、iload_、lload、lload_、fload、fload_、dload、dload_、aload、aload_

2) 將一個數值從操作數棧存儲到局部變量表:istore、istore_、lstore、lstore_、fstore、fstore_、dstore、dstore_、astore、astore_

3) 將一個常量加載到操作數棧:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_、lconst_、fconst_、dconst_

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

iload_指 iload_0、iload_1、iload_2 等指令

運算指令

運算指令用於對兩個操作數棧上的值進行某種運算,並把結果重新存入到操作數棧頂。包括:

1) 加法指令:iadd、ladd、fadd、dadd

2) 減法指令:isub、lsub、fsub、dsub

3) 按位或指令:ior、lor

4) ……

類型轉換指令

Java 虛擬機直接支持以下幾種寬化類型轉換:
1) int 類型到 long、float、double
2) long 類型到 float、double
3) flaot 類型到 double

相反,如果是窄化類型轉換,必須顯式地使用轉換指令:i2b、i2c、i2s、l2i……等等。

將一個 float 轉換爲整數類型 T(int 或 long) 時,遵循以下原則:
1) 如果 float 值爲 NaN,則 T 爲 0
2) 如果 float 不是無窮大,則使用 IEEE 754 的向零舍入模式取整,獲得整數 v,如果 v 在 T 的表示範圍之內,則轉換結果爲 v
3) 否則,將根據 v 的符號,轉換爲 T 所能表示的最大或最小正數

對象創建和訪問指令

類實例和數組都是對象,創建和訪問指令包括:

1) new

2) newarray、anewarray 等

3) getfiled、putfield 等

4) ……

操作數棧管理指令

包括:

1) pop、pop2

2) dup、dup2 等

3) swap

控制轉移指令

包括:

1) ifeq、iflt、ifle 等

2) tableswitch、lookupswitch

3) goto、goto_w 等

方法調用和返回指令

方法調用將在後面的章節講解,下面列舉幾條:

1) invokevirtual,多態

2) invokeinterface,接口方法

3) invokespecial,需要特殊處理的實例方法,比如實例初始化方法、私有方法和父類方法

4) invokestatic,靜態方法

5) invokedynamic

方法返回指令包括 ireturn、lretun 等

異常處理指令

顯式拋出異常的操作都由 athrow 來完成,而其他許多運行時異常由虛擬機自動拋出,另外,異常的處理是通過異常表而不是字節碼指令完成的

同步指令

Java 虛擬機可以支持方法級的同步和方法內部一段指令序列的同步,這兩種同步結構都是使用管程來支持的。

方法級的同步是隱式的,無需字節碼指令來控制,虛擬機可根據 ACC_SYNCHRONIZED 訪問標誌得知一個方法是否聲明爲同步方法。

同步一段指令序列集通常是由 Java 中的 synchronized 語句塊表示的,Java 虛擬機的指令集有 moniterenter 和 moniterexit 兩條指令來支持 synchronized 關鍵字的語義。

編譯器必須確保無論方法通過什麼方式來完成,每個 moniterenter 指令都必須有一條對應的 moniterexit 指令,無論這個方法是正常結束還是異常結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章