第6章-類文件結(jié)構(gòu)

[TOC]

6.2 無關(guān)性的基石

  1. 各種不同平臺的虛擬機與所有平臺都統(tǒng)一使用的程序存儲格式——字節(jié)碼(ByteCode)是構(gòu)成平臺無關(guān)性的基石。

  2. 實現(xiàn)語言無關(guān)性的基礎(chǔ)仍然是虛擬機和字節(jié)碼存儲格式。Java虛擬機不和包括 Java在內(nèi)的任何語言綁定,它只與 “Class文件” 這種特定的二進制文件格式所關(guān)聯(lián)。

  3. Class文件中包含了Java虛擬機指令集和符號表以及若干其他輔助信息?;诎踩矫娴目紤],Java虛擬機規(guī)范要求在Class文件中使用許多強制性的語法和結(jié)構(gòu)化約束,但任一門功能性語言都可以表示為一個能被 Java虛擬機所接受的有效的Class文件。

語言無關(guān)性

6.3 Class類文件的結(jié)構(gòu)

6.3.0 概述

  1. Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進制流,各個數(shù)據(jù)項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符,這使得整個Class文件中存儲的內(nèi)容幾乎全部是程序運行的必要數(shù)據(jù),沒有空隙存在。當遇到需要占用8位字節(jié)以上空間的數(shù)據(jù)項時,則會按照高位在前的方式分割成若干個8位字節(jié)進行存儲。
  2. Class文件采用一種類似C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲數(shù)據(jù),這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號數(shù)和表
    • 無符號數(shù)屬于基本的數(shù)據(jù)類型,以u1、u2、u4、u8來分別代表1個字節(jié)、2個字節(jié)、4個字節(jié)和8個字節(jié)。無符號數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。
    • 表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構(gòu)成的復合數(shù)據(jù)類型,所有表都習慣性地“_info”結(jié)尾。表用于描述有層次關(guān)系的復合結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)上就是一張表,它由如下所示的數(shù)據(jù)項構(gòu)成:
Class文件格式
  1. 無論是無符號數(shù)還是表,當需要描述同一類型但數(shù)量不定的多個數(shù)據(jù)時,經(jīng)常會使用一個前置的容量計數(shù)器加若干個連續(xù)的數(shù)據(jù)項的形式,這時稱這一系列連續(xù)的某一類型的數(shù)據(jù)為某一類型的集合。

  2. Class的結(jié)構(gòu)不像XML等描述語言,由于它沒有任何分隔符號,所以所有的數(shù)據(jù)項,無論是順序還是數(shù)量,甚至于數(shù)據(jù)存儲的字節(jié)序(Byte Ordering,Class文件中字節(jié)序為Big-Endian)這樣的細節(jié),都是被嚴格限定的,哪個字節(jié)代表什么含義,長度是多少,先后順序如何,都不允許改變。

  3. 為了更好地理解,后續(xù)利用如下代碼的編譯結(jié)果進行實例展示( JDK1.6 ):

package jvm;
 
public class SimpleClass implements Comparable<SimpleClass> 
{
    private static final int magic = 0xCAFEBABE;

    private int number;

    public void setNumber(int number) 
    {
        this.number = number;
    }

    public int compareTo(SimpleClass o) 
    {
        if (this.number == o.number) 
        {
            return 0;
        }

        int ret = this.number > o.number ? 1 : -1;
        return ret;
    }
}

6.3.1 魔數(shù)與Class文件的版本

  1. 每個Class文件的頭4個字節(jié)稱為魔數(shù)(Magic Number),它的唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件。
  2. 使用魔數(shù)而不是擴展名來進行識別主要是基于安全方面的考慮,因為文件擴展名可以隨意地改動。文件格式的制定者可以自由地選擇魔數(shù)值,只要這個魔數(shù)值還沒有被廣泛采用過同時又不會引起混淆即可。
  3. Class文件的魔數(shù)的獲得很有“浪漫氣息”,值為:0xCAFEBABE(咖啡寶貝)
  4. 緊接著魔數(shù)的4個字節(jié)存儲的是Class文件的版本號:其中第5和第6個字節(jié)是次版本號(Minor Version),第7和第8個字節(jié)是主版本號(Major Version)。
  5. Java的版本號是從45開始的,JDK1.1之后的每個JDK大版本發(fā)布主版本號向上加1(JDK1.01.1使用了45.045.3的版本號),高版本的 JDK能向下兼容以前版本的Class文件,但不能運行以后版本的Class文件,即使文件格式并未發(fā)生任何變化,虛擬機也必須拒絕執(zhí)行超過其版本號的Class文件。
  6. 對于 JDK版本1.8,可生成的Class文件主版本號最大值為52.0(0x0034)。

6.3.2 常量池

  1. 緊接著主次版本號之后的是常量池入口,常量池可以理解為Class文件之中的資源倉庫,它是Class文件結(jié)構(gòu)中與其他項目關(guān)聯(lián)最多的數(shù)據(jù)類型,也是占用Class文件空間最大的數(shù)據(jù)項目之一,同時它還是在Class文件中第一個出現(xiàn)的表類型數(shù)據(jù)項目。

  2. 由于常量池中常量的數(shù)量是不固定的,所以在常量池的入口需要放置一項 u2 類型的數(shù)據(jù),代表常量池容量計數(shù)值(constant_pool_count)。

  3. 與Java中語言習慣不一樣的是,這個容量計數(shù)是從1而不是0開始的,如果該值的十進制表示為22,這就代表常量池中有21項常量,索引值范圍為1~21。(實際常量數(shù) = Class文件常量數(shù) - 1)

  4. 在Class文件格式規(guī)范制定之時,設(shè)計者將第0項常量空出來是有特殊考慮的:這樣做的目的在于滿足后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下需要表達 “不引用任何一個常量池項目” 的含義,這種情況就可以把索引值置為0來表示。

  5. Class文件結(jié)構(gòu)中只有常量池的容量計數(shù)是從1開始,對于其他集合類型,包括接口索引集合、字段表集合、方法表集合等的容量計數(shù)都與一般習慣相同,是從0開始的。

  6. 常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。

    • 字面量比較接近于Java語言層面的常量概念,如文本字符串、聲明為final的常量值等。
    • 符號引用則屬于編譯原理方面的概念,包括了下面三類常量:
      • 類和接口的全限定名(Fully Qualified Name)
      • 字段的名稱和描述符(Descriptor)
      • 方法的名稱和描述符
  7. Java代碼在進行 Javac 編譯的時候,并不像C和C++那樣有 “連接” 這一步驟,而是在虛擬機加載Class文件的時候進行動態(tài)連接。也就是說,在Class文件中不會保存各個方法、字段的最終內(nèi)存布局信息,因此這些字段、方法的符號引用不經(jīng)過運行期轉(zhuǎn)換的話無法得到真正的內(nèi)存入口地址,也就無法直接被虛擬機使用。當虛擬機運行時,需要從常量池獲得對應的符號引用,再在類創(chuàng)建時或運行時解析、翻譯到具體的內(nèi)存地址之中。

  8. 常量池中每一項常量都是一個表,在 JDK1.7之前共有11種結(jié)構(gòu)各不相同的表結(jié)構(gòu),,JDK1.7又增加了3種。這14種表都有一個共同的特點,就是表開始的第一位是一個u1類型的標志位(tag,取值見下表),代表當前這個常量屬于哪種常量類型。

常量池項目類型

6.3.2.1 CONSTANT_Class_info 型常量的結(jié)構(gòu)

  1. CONSTANT_Class_info類型的常量代表一個類或者接口的符號引用,它的結(jié)構(gòu)比較簡單:
CONSTANT_Class_info
  1. 其中tag是標志位,它用于區(qū)分常量類型;name_index是一個索值;它指向常量池中一個CONSTANT_Utf8_info類型常量,此常量代表了這個類(或者接口)的全限定名。

6.3.2.2 CONSTANT_Utf8_info 型常量的結(jié)構(gòu)

CONSTANT_Utf8_info
  1. length值說明了這個UTF-8編碼的字符串長度是多少字節(jié),它后面緊跟著的長度為 length 字節(jié)的連續(xù)數(shù)據(jù)是一個使用UTF-8縮略編碼表示的字符串。

  2. UTF-8縮略編碼與普通UTF-8編碼的區(qū)別是:

    • 從 '\u0001’ 到 ‘\u007f’ 之間的字符(相當于1~127的ASCII碼)的縮略編碼使用一個字節(jié)表示。
    • 從 '\u0080’ 到 ‘\u07ff' 之間的所有字符的縮略編碼用兩個字節(jié)表示。
    • 從 ‘\u0800' 到 ‘\uffff' 之間的所有字符的縮略編碼就按照普通UTF-8編碼規(guī)則使用三個字節(jié)表示。。
  3. 由于Class文件中方法、字段等都需要引用 CONSTANT_Utf8_info 型常量來描述名稱,所以CONSTANT_Utf8_info 型常量的最大長度也就是 Java中方法、字段名的最大長度。

  4. 而這里的最大長度就是 length 的最大值,即 u2 類型能表達的最大值65535。所以 Java程序中如果定義了超過 64KB 英文字符的變量或方法名,將會無法編譯。

  5. 使用 javap工具可用于分析Class文件字節(jié)碼。

6.3.2.3 常量池中的14種常量項的結(jié)構(gòu)總表

常量項結(jié)構(gòu)總表1
常量項結(jié)構(gòu)總表2

6.3.2.4 實例分析(6.3.1~6.3.2)

部分Class文件
  1. 如上圖所示:Class文件的魔數(shù)值為:0xCAFEBABE。
  2. 緊接著魔數(shù)的4個字節(jié)存儲的是Class文件的版本號:第5和第6個字節(jié)是次版本號(Minor Version,圖中為0x0000),第7和第8個字節(jié)是主版本號(Major Version,圖中為0x0031)
  3. 常量池容量 (偏移地址:0x00000008) 為十六進制數(shù) 0x0024,即十進制的36,這就代表常量池中有35項常量,索引值范圍為1~35。
  4. 常量池的第一項常量的標志位 (偏移地址:0x0000000a) 是 0x07,表示這個常量屬于CONSTANT_Class_info類型
  5. CONSTANT_Class_info類型的 name_index 值 (偏移地址:0x0000000b) 為0x0002,也即是指向了常量池中的第二項常量。 它的標志位 (地址:0x0000000d) 是0x01,表示這個常量屬于CONSTANT_Utf8_info類型
  6. CONSTANT_Utf8_info類型的 length 值 (偏移地址:0x0000000e) 為0x000F,也就是長15字節(jié),往后15字節(jié)正好都在1~127的ASCII碼范圍以內(nèi),內(nèi)容為 jvm/SimpleClass
  7. 剩下的常量 (從偏移地址 0x0000001f 開始) 可以通過類似的方法計算出來

6.3.3 訪問標志

  1. 在常量池結(jié)束之后,緊接著的兩個字節(jié)代表訪問標志(access_fags),這個標志用于識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義為public類型;是否定義為abstract類型;如果是類的話,是否被聲明為final等。
訪問標志
  1. access_flags中一共有16個標志位可以使用,當前只定義了其中8個,沒有使用到的標志位要求一律為0。在計算時將所有符合要求的標志位取出,然后做異或得到最終結(jié)果。

  2. 例如一個普通類不是接口、枚舉或者注解,被 public 關(guān)鍵字修飾但沒有被聲明為 final 和 abstract,并且它使用了 JDK1.2之后的編譯器進行編譯,因此它的 ACC_PUBLIC、ACC_SUPER 標志應當為,而 ACC_FINAL、ACC_INTERFACE、ACC_ABSTRACT、ACC_SYNTHETIC、ACC_ANNOTATION、ACC_ENUM 這6個標志應當為,因此它的 access_flags 的值應為:0001 | 0020 = 0021

6.3.4 類索引、父類索引與接口索引集合

  1. 類索引(this_class)和父類索引(super_class)都是一個u2類型的數(shù)據(jù),而接口索引集合(interfaces)是一組 u2 類型的數(shù)據(jù)的集合,Class文件中由這三項數(shù)據(jù)來確定這個類的繼承關(guān)系。
  2. 類索引用于確定這個類的全限定名,父類索引用于確定這個類的父類的全限定名。
  3. 由于 Java語言不允許多重繼承,所以父類索引只有一個,除了 java.lang.Object 之外,所有的 Java類都有父類,因此除了 java.lang.Object外,所有 Java類的父類索都不為0。
  4. 接口索引集合就用來描述這個類實現(xiàn)了哪些接口,這些被實現(xiàn)的接口將按 implements語句(如果這個類本身是一個接口,則應當是extends語句)后的接口順序從左到右排列在接口索引集合中。
  5. 類索引、父類索引和接口索引集合都按順序排列在訪問標志之后,類索引和父類索引用兩個 u2 類型的索引值表示,它們各自指向一個類型為 CONSTANT_Class_info 的類描述符常量,通過CONSTANT_Class_info類型的常量中的索引值以找到定義在 CONSTANT_Utf8_info 類型的常量中的全限定名字符串。
  6. 對于接口索引集合,入口的第一項——u2 類型的數(shù)據(jù)為接口計數(shù)器(interfaces count),表示索引表的容量。如果該類沒有實現(xiàn)任何接口,則該計數(shù)器值為0,后面接口的索引表不再占用任何字節(jié)。

6.3.4.1 實例分析 (6.3.3~6.3.4)

類索引查找

部分常量池

6.3.5 字段表集合

  1. 字段表(field_info)用于描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量,但不包括在方法內(nèi)部聲明的局部變量。
  2. 一個字段可以包含的信息有:
    • 字段的作用域(public、private、protected修飾符)
    • 是實例變量還是類變量(static修飾符)
    • 可變性(final)
    • 并發(fā)可見性(volatile修飾符,是否強制從主內(nèi)存讀寫)
    • 可否被序列化(transient修飾符)
    • 字段數(shù)據(jù)類型(基本類型、對象、數(shù)組)
    • 字段名稱

上述這些信息中,各個修飾符都是布爾值,要么有某個修飾符,要么沒有,很適合使用標志位來表示。而字段叫什么名字、字段被定義為什么數(shù)據(jù)類型,這些都是無法固定的,只能引用常量池中的常量來描述。

字段表格式

6.3.5.1 字段修飾符

  1. 該字段放在 access_flags 項目中,它與類中的 access_flags 項目是非常類似的,都是一個 u2 的數(shù)據(jù)類型,其中可以設(shè)置的標志位和含義如下:
字段訪問標志
  1. 很明顯,在實際情況中:
    • ACC_PUBLIC、ACC_PRIVATE、ACC_PROTEETED三個標志最多只能選擇其一
    • ACC_FINAL、ACC_VOLATILE 不能同時選擇
    • 接口之中的字段必須有 ACC_PUBLIC、ACC_STATIC、ACC_FINAL標志
    • 這些都是由 Java 本身的語言規(guī)則所決定的

6.3.5.2 常量池索引

  1. 跟隨 access_flags 標志的是兩項索引值:name_index 和 descriptor_index。它們都是對常量池的引用,分別代表著字段的簡單名稱以及字段和方法的描述符。
  2. 所謂全限定名,僅僅是把類全名中的 “.” 替換成了 “/” 而已,為了使連續(xù)的多個全限定名之間不產(chǎn)生混淆,在使用時最后一般會加入一個 “;” 表示全限定名結(jié)束。
  3. 簡單名稱是指沒有類型和參數(shù)修飾的方法或者字段名稱,例如 inc () 方法的簡單名稱是 “inc”
  4. 描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量、類型以及順序)和返回值。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型(byte、char、double、float、int、long、short、boolean)以及代表無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符 L 加對象的全限定名來表示。
描述符
  1. 對于數(shù)組類型,每一維度將使用一個前置的 [ 字符來描述,如一個定義為 java.lang.String[][]類型的二維數(shù)組,將被記錄為:[Ljava/lang/String,一個整型數(shù)組 int[] 將被記錄為 [I 。
  2. 用描述符來描述方法時,按照先參數(shù)列表,后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴格順序放在一組小括號 () 之內(nèi)。
    • 如方法 void inc () 的描述符為 ()V
    • 方法 java.lang.String.toString () 的描述符為 ()Ljava/lang/String
    • 方法 int indexOf (char[] source,int sourceOffset,int sourceCount,char[] target,int targetOffset,int targetCount,int fromlndex) 的描述符為 ([CII[CIII)I

6.3.5.3 字段表的屬性表集合

  1. 字段表都包含的固定數(shù)據(jù)項目到 descriptor_index 為止就結(jié)束了,不過在 descriptor_index 之后跟隨著一個屬性表集合用于存儲一些額外的信息,字段都可以在屬性表中描述零至多項的額外信息。
  2. 字段表集合中不會列出從超類或者父接口中繼承而來的字段,但有可能列出原本 Java 代碼之中不存在的字段,譬如在內(nèi)部類中為了保持對外部類的訪問性,會自動添加指向外部類實例的字段。
  3. 另外,在 Java 語言字段是無法重載的,兩個字段的數(shù)據(jù)類型、修飾符不管是否相同,都必須使用不一樣的名稱,但是對于字節(jié)碼來講,如果兩個字段的描述符不一致,那字段重名就是合法的。

6.3.5.4 實例分析(6.3.5)

  1. 對于SimpleClass.class文件來說,字段表集合從地址 0x000001bb 開始,第一個 u2 類型的數(shù)據(jù)為容量計數(shù)器 fields_count,其值為0x0002,說明這個類有兩個字段表數(shù)據(jù)。
  2. 接下來緊跟著容量計數(shù)器的是第一個字段的 access_flags 標志,值為 0x001A,代表該字段被private+static+final 修飾。
  3. 代表字段名稱的 name_index 的值為 0x0007,從常量池中可查得第7項常量名為 “magic”
  4. 代表字段描述符的 descriptor_index 的值為0x0008,指向常量池的字符串 “I”,即該字段為int類型
字段表
常量池

6.3.6 方法表集合

  1. Class文件存儲格式中對方法的描述與對字段的描述幾乎采用了完全一致的方式。方法表的結(jié)構(gòu)如同字段表一樣,依次包括了訪問標志(access_flags)、名稱索引(nameindex)、描述符索引(descriptor_index)、屬性表集合(attributes)幾項。這些數(shù)據(jù)項目的含義也非常類似,僅在訪問標志和屬性表集合的可選項中有所區(qū)別。
方法表格式
  1. 因為 volatile 關(guān)鍵字和 transient 關(guān)鍵字不能修飾方法,所以方法表的訪問標志中沒有了 ACC_VOLATILE 標志和 ACC_TRANSIENT 標志。與之相對的,synchronized、native、strictfp 和 abstract 關(guān)鍵字可以修飾方法,所以方法表的訪問標志中增加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP 和 ACC_ABSTRACT 標志,所有標志位及其取值可見下表
方法訪問標志
  1. 方法的定義可以通過訪問標志、名稱索引、描述符索引表達清楚,但方法里面的代碼去哪里了?方法里的 Java代碼,經(jīng)過編譯器編譯成字節(jié)碼指令后,存放在方法屬性表集合中一個名為 “Code” 的屬性里面,屬性表作為 Class 文件格式中最具擴展性的一種數(shù)據(jù)項目,將在6.3.7節(jié)中詳細講解。

  2. 與字段表集合相對應的,如果父類方法在子類中沒有被重寫(Override),方法表集合中就不會出現(xiàn)來自父類的方法信息。但同樣的,有可能會出現(xiàn)由編譯器自動添加的方法,最典型的便是類構(gòu)造器 <clinit> 方法和實例構(gòu)造器 <inits> 方法。

  3. 在 Java語言中,要重載(Overload)一個方法,除了要與原方法具有相同的簡單名稱之外,還要求必須擁有一個與原方法不同的特征簽名

    • 特征簽名就是一個方法中各個參數(shù)在常量池的字段符號引用的集合。
    • Java 代碼的方法特征簽名只包括了方法名稱、參數(shù)順序及參數(shù)類型,即不包含返回值,因此 Java 語言里面是無法僅僅依靠返回值的不同來對一個已有方法進行重載的。
    • 但是在 Class 文件格式中,特征簽名的范圍更大一些,還包括方法返回值以及受查異常表,只要描述符不是完全一致的兩個方法也可以共存。也就是說,如果兩個方法有相同的名稱和特征簽名,但返回值不同,那么也是可以合法共存于同一個Class文件中的。

6.3.6.1 實例分析(6.3.6)

  1. 方法表集合的入口地址為:0x00001d4,第一個 u2 類型的數(shù)據(jù) (即計數(shù)器容量) 的值為0x0004,代表集合中有四個方法,這四個方法為編譯器添加的實例構(gòu)造器、Comparable接口的構(gòu)造方法以及源碼中的方法 compareTo() 和 setNumber()。
  2. 第一個方法的訪問標志值為0x0001,也就是只有 ACC_PUBLIC 標志為真
  3. 名稱索引值為 0x000C,從常量池中可查得第12項常量池的方法名為 <init>
  4. 描述符索引值為 0x000D,對應常量為 ()V
  5. 屬性表計數(shù)器 attributes_count 的值為 0x0001,表示此方法的屬性表集合有一項屬性,屬性名稱索引為0x000E,對應常量為 Code,說明此屬性是方法的字節(jié)碼描述。
方法表
常量池

6.3.7 屬性表集合

  1. 屬性表(attribute_info)在前面已經(jīng)出現(xiàn)過數(shù)次,在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,以用于描述某些場景專有的信息。
  2. 與 Class 文件中其他的數(shù)據(jù)項目要求嚴格的順序、長度和內(nèi)容不同,屬性表集合的限制稍微寬松了一些,不再要求各個屬性表具有嚴格順序,并且只要不與已有屬性各重復,任何人實現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱约憾x的屬性信息,Java 虛擬機運行時會忽略掉它不認識的屬性。
預定義屬性1
預定義屬性2
  1. 對于每個屬性,它的名稱需要從常量池中引用一個 CONSTANT_Utf8_info 類型的常量來表示,而屬性值的結(jié)構(gòu)則是完全自定義的,只需要通過一個 u4 的長度屬性去說明屬性值所占用的位數(shù)即可。一個符合規(guī)則的屬性表應該滿足下表中所定義的結(jié)構(gòu):
屬性表結(jié)構(gòu)

6.3.7.1 Code屬性

  1. Java 程序方法體中的代碼經(jīng)過 Javac 編譯器處理后,最終變?yōu)樽止?jié)碼指令存儲在Code屬性內(nèi)。Code 屬性出現(xiàn)在方法表的屬性集合之中,但并非所有的方法表都必須存在這個屬性,譬如接口或者抽象類中的方法就不存在Code屬性,如果方法表有Code屬性存在,那么它的結(jié)構(gòu)如下圖所示:
Code屬性表
  1. attribute_name_index 是一項指向 CONSTANT_Utf8_info 型常量的索引,常量值固定為 “Code”,它代表了該屬性的屬性名稱

  2. attribute_length 指示了屬性值的長度,由于屬性名稱索引與屬性長度一共為6字節(jié),所以屬性值的長度固定為整個屬性表長度減去6個字節(jié)。

  3. max_stack 代表了操作數(shù)棧(Operand Stacks)深度的最大值。在方法執(zhí)行的任意時刻,操作數(shù)棧都不會超過這個深度。虛擬機運行的時候需要根據(jù)這個值來分配棧幀(Stack Frame)中的操作棧深度。

  4. max_locals 代表了局部變量表所需的存儲空間。在這里,max_locals 的單位是Slot

    • Slot 是虛擬機為局部變量分配內(nèi)存所使用的最小單位。對于byte、char、float、int、short、boolean 和 returnAddress 等長度不超過32位的數(shù)據(jù)類型,每個局部變量占用 1 個Slot,而double 和 long這兩種64位的數(shù)據(jù)類型則需要兩個Slot來存放。
    • 方法參數(shù)(包括實例方法中的隱藏參數(shù) “this”)、顯式異常處理器的參數(shù)(Exception Handler Parameter,就是try-catch語句中catch塊所定義的異常)、方法體中定義的局部變量都需要使用局部變量表來存放。
    • 另外,并不是在方法中用到了多少個局部變量,就把這些局部變量所占 Slot 之和作為max_locals的值,原因是局部變量表中的 Slot 可以重用,當代碼執(zhí)行超出一個局部變量的作用域時,這個局部變量所占的Slot可以被其他局部變量所使用,Javac 編譯器會根據(jù)變量的作用域來分配 Slot 給各個變量使用,然后計算出max_locals的大小。
  5. code_length 和 code 用來存儲 Java 源程序編譯后生成的字節(jié)碼指令。code_length代表字節(jié)碼長度,code是用于存儲字節(jié)碼指令的一系列字節(jié)流。既然叫字節(jié)碼指令,那么每個指令就是一個u1類型的單字節(jié),當虛擬機讀取到 code 中的一個字節(jié)碼時,就可以對應找出這個字節(jié)碼代表的是什么指令,并且可以知道這條指令后面是否需要跟隨參數(shù),以及參數(shù)應當如何理解。

  6. 一個u1數(shù)據(jù)類型的取值范圍為 0x000xFF,對應十進制的0255,也就是一共可以表達256條指令

  7. 雖然 code_length 是一個 u4 類型的長度值,理論上最大值可以達到 2^32 - 1,但是虛擬機規(guī)范中明確限制了一個方法不允許超過 65535 條字節(jié)碼指令,即它實際只使用了 u2 的長度,如果超過這個限制,Javac 編譯器也會拒絕編譯。

  8. Code 屬性是Class文件中最重要的一個屬性,如果把一個 Java程序中的信息分為代碼(Code,方法體里面的 Java代碼)和元數(shù)據(jù)(Metadata,包括類、字段、方法定義及其他信息)兩部分,那么在整個Class文件中,Code屬性用于描述代碼,所有的其他數(shù)據(jù)項目都用于描述元數(shù)據(jù)。

  9. 在字節(jié)碼指令之后的是這個方法的顯式異常處理表(下文簡稱異常表)集合,異常表對于Code屬性來說并不是必須存在的,它的格式如下。其中字段的含義為:如果當字節(jié)碼在第 start_pc 行到第 end_pe 行之間(不含第 end_pc 行)出現(xiàn)了類型為 catch_type 或者其子類的異常(catch_type為指向一個CONSTANT_Class_info型常量的索引),則轉(zhuǎn)到第 handler_pc 行繼續(xù)處理。當 catch_type 的值為0時,代表任意異常情況都需要轉(zhuǎn)向到 handler_pc 處進行處理。

異常表

6.3.7.2 Exceptions屬性

  1. Exceptions 屬性的作用是列舉出方法中可能拋出的受查異常(Checked Excepitons),也就是方法描述時在 throws 關(guān)鍵字后面列舉的異常。它的結(jié)構(gòu)如下:
Exceptions屬性
  1. Exceptions 屬性中的 number_of_exceptions 項表示方法可能拋出 number_of_exceptions 受查異常,每一種受查異常使用一個 exception_index_table 項表示,exception_index_table 是一個指向常量池中CONSTANT_Class_info 型常量的索引,代表了該受查異常的類型。

6.3.7.3 LineNumberTable屬性

  1. LineNumberTable 屬性用于描述 Java 源碼行號與字節(jié)碼行號(字節(jié)碼的偏移量)之間的對應關(guān)系。它并不是運行時必需的屬性,但默認會生成到Class文件之中,可以在 Javac 中分別使用 -g:none-g:lines 選項來取消或要求生成這項信息,結(jié)構(gòu)如下:
LineNumberTable屬性
  1. line_number_table 是一個數(shù)量為 line_number_table_length、類型為 line number_info 的集合,line_number_info 表包括了 start_peline_number 兩個 u2 類型的數(shù)據(jù)項,前者是字節(jié)碼行號,后者是 Java 源碼行號。

6.3.7.4 LocalVariable Table 屬性

  1. LocalVariableTable 屬性用于描述棧幀中局部變量表中的變量與 Java 源碼中定義的變量之間的關(guān)系,它也不是運行時必需的屬性,但默認會生成到Class文件之中,可以在 Javac 中分別使用 -g:none-g:vars選項來取消或要求生成這項信息。結(jié)構(gòu)如下:
LocalVariable Table 屬性
  1. 其中,local_variable_info 項目代表了一個棧幀與源碼中的局部變量的關(guān)聯(lián),結(jié)構(gòu)如下:
local_variable_info結(jié)構(gòu)
  1. start_pclength 屬性分別代表了這個局部變量的生命周期開始的字節(jié)碼偏移量及其作用范圍覆蓋的長度,兩者結(jié)合起來就是這個局部變量在字節(jié)碼之中的作用域范圍。
  2. name_indexdescriptor_index 都是指向常量池中 CONSTANT_Utf8_info 型常量的索引,分別代表了局部變量的名稱以及這個局部變量的描述符。
  3. index 是這個局部變量在棧幀局部變量表中Slot 的位置。當這個變量數(shù)據(jù)類型是64位類型時,它用的 Slot 為index 和 index + 1兩個。
  4. 在 JDK1.5 引入泛型之后,LocalVariableTable 屬性增加了一個 “姐妹屬性” :LocalVariableTypeTable,這個新增的屬性結(jié)構(gòu)僅僅是把記錄的字段描述符的 descriptor_index 替換成了字段的特征簽名(Signature),對于非泛型類型來說,描述符和特征簽名能描述的信息是基本一致的,但是泛型引入之后,由于描述符中泛型的參數(shù)化類型被擦除掉,描述符就不能準確地描述泛型類型了,因此出現(xiàn)了LocalVariableTypeTable。

6.3.7.5 SourceFile屬性

  1. SourceFile 屬性用于記錄生成這個Class文件的源碼文件名稱。這個屬性也是可選的,可以分別使用 Javac 的 -g:none-g:source選項來關(guān)閉或要求生成這項信息。在 Java中,對于大多數(shù)的類來說,類名和文件名是一致的,但是有一些特殊情況(如內(nèi)部類)例外。這個屬性是一個定長的屬性,結(jié)構(gòu)如下:
SourceFile屬性
  1. sourcefile_index 數(shù)據(jù)項是指向常量池中 CONSTANT_Utf8_info 型常量的索引,常量值是源碼文件的文件名。

6.3.7.6 ConstantValue 屬性

  1. ConstantValue屬性的作用是通知虛擬機自動為靜態(tài)變量賦值。只有被 static 關(guān)鍵字修飾的變量(類變量)才可以使用這項屬性。
  2. 對于非static類型的變量(也就是實例變量)的賦值是在實例構(gòu)造器 <init> 方法中進行的;而對于類變量,則有兩種方式可以選擇:在類構(gòu)造器 <clinit> 方法中或者使用 ConstantValue 屬性。
  3. 目前Sun Javac 編譯器的選擇是:
    • 如果同時使用 final 和 static 來修飾一個變量(按照習慣,這里稱“常量”更貼切),并且這個變量的數(shù)據(jù)類型是基本類型或者 java.lang.String 的話,就生成ConstantValue屬性來進行初始化
    • 如果這個變量沒有被 final 修飾或者并非基本類型及字符串,則將會選擇在<clinit>方法中進行初始化。
image
  1. 從數(shù)據(jù)結(jié)構(gòu)中可以看出,ConstantValue屬性是一個定長屬性,它的attribute_length數(shù)據(jù)項值必須固定為2。constantvalue_index 數(shù)據(jù)項代表了常量池中一個字面量常量的引用,根據(jù)字段類型的不同,字面量可以是CONSTANT_Long_info, CONSTANT_Float_info, CONSTANT_Double_info, CONSTANT_Integer_info, CONSTANT_String_info 常量中的一種。

6.3.7.7 InnerClasses屬性

  1. InnerClasses屬性用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)。如果一個類中定義了內(nèi)部類,那編譯器將會為它以及它所包含的內(nèi)部類生成 InnerClasses 屬性,結(jié)構(gòu)如下:
InnerClasses屬性
  1. 數(shù)據(jù)項 number_of_classes 代表需要記錄多少個內(nèi)部類信息,每一個內(nèi)部類的信息都由一個 inner_classes_info 表進行描述,結(jié)構(gòu)如下:
inner_classes_info表
  1. inner_class_info_indexouter_class_info_index 都是指向常量池中CONSTANT_Class_info 型常量的索引,分別代表了內(nèi)部類和宿主類的符號引用。

  2. inner_name_index 是指向常量池中 CONSTANT_Utf8_info 型常量的索引,代表這個內(nèi)部類的名稱,如果是匿名內(nèi)部類,那么這項值為0。

  3. inner_class_access_flags 是內(nèi)部類的訪問標志,類似于類的 access_flags,它的取值范圍如下:

inner_class_access_flags取值

6.3.7.8 Deprecated及Synthetic屬性

  1. Deprecated 和 Synthetic兩個屬性都屬于標志類型的布爾屬性,只存在有和沒在的區(qū)別,沒有屬性值的概念。
  2. Deprecated 屬性用于表示某個類、字段或者方法,已經(jīng)被程序作者定為不再推薦使用,它可以通過在代碼中使用 @deprecated 注釋進行設(shè)置。
  3. Synthetic 屬性代表此字段或者方法并不是由 Java 源碼直接產(chǎn)生的,而是由編譯器自行添加的。所有由非用戶代碼產(chǎn)生的類、方法及字段都應當至少設(shè)置 Synthetic 屬性和 ACC_SYNTHETIC 標志位中的一項,唯一的例外是實例構(gòu)造器<init>方法和類構(gòu)造器<clini>方法
Deprecated及Synthetic屬性
  1. 其中attribute_length數(shù)據(jù)項的值必須為0x00000000,因為沒有任何屬性值需要設(shè)置。

6.3.7.9 StackMapTable屬性

  1. StackMapTable 屬性在 JDK1.6 發(fā)布后增加到了Class文件規(guī)范中,它是一個復雜的變長屬性,位于Code屬性的屬性表中。這個屬性會在虛擬機類加載的字節(jié)碼驗證階段被新類型檢查驗證器(Type Checker)使用,目的在于代替以前比較消耗性能的基于數(shù)據(jù)流分析的類型推導驗證器。
  2. StackMapTable 屬性中包含零至多個棧映射幀(Stack Map Frames),每個棧映射幀都顯式或隱式地代表了一個字節(jié)碼偏移量,用于表示執(zhí)行到該字節(jié)碼時局部變量表和操作數(shù)棧的驗證類型。類型檢查驗證器會通過檢查目標方法的局部變量和操作數(shù)棧所需要的類型來確定一段字節(jié)碼指令是否符合邏輯約束。StackMapTable屬性的結(jié)構(gòu)見表6-27。
StackMapTable屬性

6.3.10 Signature屬性

  1. Signature 屬性在 JDK1.5 發(fā)布后增加到了Class文件規(guī)范之中,它是一個可選的定長屬性,可以出現(xiàn)于類、屬性表和方法表結(jié)構(gòu)的屬性表中。
  2. 在 JDK1.5 中大幅增強了 Java 語言的語法,在此之后,任何類、接口、初始化方法或成員的泛型簽名如果包含了類型變量(Type Variables)或參數(shù)化類型(Parameterized Types),則Signature屬性會為它記錄泛型簽名信息。
  3. 之所以要專門使用這樣一個屬性去記錄泛型類型,是因為 Java 語言的泛型采用的是擦除法實現(xiàn)的偽泛型,在字節(jié)碼(Code屬性)中,泛型信息編譯(類型變量、參數(shù)化類型)之后都通通被擦除掉。
  4. 使用擦除法的好處是實現(xiàn)簡單(主要修改 Javac 編譯器,虛擬機內(nèi)部只做了很少的改動)、非常容易實現(xiàn)Backport,運行期也能夠節(jié)省一些類型所占的內(nèi)存空間。
  5. 但壞處是運行期就無法像 C# 等有真泛型支持的語言那樣,將泛型類型與用戶定義的普通類型同等對待,例如運行期做反射時無法獲得到泛型信息。
  6. Signature屬性就是為了彌補這個缺陷而增設(shè)的,現(xiàn)在 Java 的反射API能夠獲取泛型類型,最終的數(shù)據(jù)來源也就是這個屬性。
Signature屬性
  1. 其中 signature_index 項的值必須是一個對常量池的有效索引。常量池在該索引處的項必須是 CONSTANT_Utf8_info結(jié)構(gòu),表示類簽名、方法類型簽名或字段類型簽名,根據(jù)當前的Signature屬性是類文件、方法表還是字段表的屬性來判斷。

6.3.11 BootstrapMethods屬性

  1. BootstrapMethods 屬性在 JDK1.7 發(fā)布后增加到了Class文件規(guī)范之中,它是一個復雜的變長屬性,位于類文件的屬性表中。這個屬性用于保存 invokedynamic 指令引用的引導方法限定符。
  2. 如果某個類文件結(jié)構(gòu)的常量池中曾經(jīng)出現(xiàn)過CONSTANT_InvokeDynamic_info類型的常量,那么這個類文件的屬性表中必須存在一個明確的 BootstrapMethods屬性。另外,即使CONSTANT_InvokeDynamic_info類型的常量在常量池中出現(xiàn)過多次,類文件的屬性表中最多也只能有一個BootstrapMethods屬性。
BootstrapMethods屬性
  1. 其中引用到的 bootstrap_method 結(jié)構(gòu)如下:
bootstrap_method
  1. BootstrapMethods屬性中,num_bootstrap_methods 項的值給出了引導方法限定符的數(shù)量。bootstrap_methods[] 數(shù)組的每個成員包含了一個指向常量池CONSTANT_MethodHandle結(jié)構(gòu)的索引值,它代表了一個引導方法,還包含了這個引導方法靜態(tài)參數(shù)的序列(可能為空)。bootstrap_methods[]數(shù)組中的每個成員必須包含以下3項內(nèi)容:
    • bootstrap_method_ref:必須是一個對常量池的有效索引。常量池在該索處的值必須是一個CONSTANT_MethodHandle_info結(jié)構(gòu)。
    • num_bootstrap_arguments:給出了 bootstrap_arguments[]數(shù)組成員的數(shù)量。
    • bootstrap_arguments[]:數(shù)組的每個成員必須是一個對常量池的有效索引。常量池在該索引處必須是下列結(jié)構(gòu)之一:CONSTANT_String_info、CONSTANT_Class_info、CONSTANT_Integer_info、CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_MethodHandle_info或CONSTANT_MethodType_info。

6.4 字節(jié)碼指令簡介

  1. Java虛擬機的指令由一個字節(jié)長度的、代表著某種特定操作含義的數(shù)字(稱為操作碼,Opcode)以及跟隨其后的零至多個代表此操作所需參數(shù)(稱為操作數(shù),Operands)而構(gòu)成。
  2. 由于 Java 虛擬機采用面向操作數(shù)棧而不是寄存器的架構(gòu),所以大多數(shù)的指令都不包含操作數(shù),只有一個操作碼。
  3. 字節(jié)碼指令集是一種具有鮮明特點、優(yōu)劣勢都很突出的指令集架構(gòu),由于限制了 Java 虛擬機操作碼的長度為一個字節(jié)(即0~255),這意味著指令集的操作碼總數(shù)不可能超過256條

6.4.1 字節(jié)碼與數(shù)據(jù)類型

  1. 在 Java 虛擬機的指令集中,大多數(shù)的指令都包含了其操作所對應的數(shù)據(jù)類型信息。例如,iload指令用于從局部變量表中加載 int 型的數(shù)據(jù)到操作數(shù)棧中,而 fload 指令加載的則是 float 類型的數(shù)據(jù)。這兩條指令的操作在虛擬機內(nèi)部可能會是由同一段代碼來實現(xiàn)的,但在Class文件中它們必須擁有各自獨立的操作碼。
  2. 由于Java虛擬機的操作碼長度只有一個字節(jié),所以包含了數(shù)據(jù)類型的操作碼就為指令集的設(shè)計帶來了很大的壓力:如果每一種與數(shù)據(jù)類型相關(guān)的指令都支持Java虛擬機所有運行時數(shù)據(jù)類型的話,那指令的數(shù)量恐怕就會超出一個字節(jié)所能表示的數(shù)量范圍了。因此,Java虛擬機的指令集對于特定的操作只提供了有限的類型相關(guān)指令去支持它,換句話說,指令集將會故意被設(shè)計成非完全獨立的(Java虛擬機規(guī)范中把這種特性稱為 “Not Orthogonal”,即并非每種數(shù)據(jù)類型和每一種操作都有對應的指令)。有一些單獨的指令可以在必要的時候用來將一些不支持的類型轉(zhuǎn)換為可被支持的類型。

6.4.2 加載和存儲指令

  1. 加載和存儲指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來回傳輸,這類指令包括如下內(nèi)容:
    • 將一個局部變量加載到操作棧
    • 將一個數(shù)值從操作數(shù)棧存儲到局部變量表
    • 將一個常量加載到操作數(shù)棧
    • 擴充局部變量表的訪問索引的指令

6.4.3 運算指令

  1. 運算或算術(shù)指令用于對兩個操作數(shù)棧上的值進行某種特定運算,并把結(jié)果重新存入到操作棧頂
  2. 大體上算術(shù)指令可以分為兩種:對整型數(shù)據(jù)進行運算的指令與對浮點型數(shù)據(jù)進行運算的指令,無論是哪種算術(shù)指令,都使用 Java 虛擬機的數(shù)據(jù)類型
  3. 由于沒有直接支持byte、short、char和boolean類型的算術(shù)指令,對于這類數(shù)據(jù)的運算,應使用操作 int 類型的指令代替。

6.4.4 類型轉(zhuǎn)換指令

  1. 類型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類型進行相互轉(zhuǎn)換,這些轉(zhuǎn)換操作一般用于實現(xiàn)用戶代碼中的顯式類型轉(zhuǎn)換操作,或者用來處理本節(jié)開篇所提到的字節(jié)碼指令集中數(shù)據(jù)類型相關(guān)指令無法與數(shù)據(jù)類型一一對應的問題。
  2. Java虛擬機直接支持(即轉(zhuǎn)換時無需顯式的轉(zhuǎn)換指令)以下數(shù)值類型的寬化類型轉(zhuǎn)換(Widening Numeric Conversions,即小范圍類型向大范圍類型的安全轉(zhuǎn)換):
    • int類型到long、float或者double類型。
    • long類型到float、double類型。
    • float 類型到double類型。

6.4.5 對象創(chuàng)建與訪問指令

  1. 雖然類實例和數(shù)組都是對象,但 Java 虛擬機對類實例和數(shù)組的創(chuàng)建與操作使用了不同的字節(jié)碼指令。對象創(chuàng)建后,就可以通過對象訪問指令獲取對象實例或者數(shù)組實例中的字段或者數(shù)組元素,這些指令如下。
    • 創(chuàng)建類實例的指令:new。
    • 創(chuàng)建數(shù)組的指令:newarray、anewarray、multianewarray。
    • 訪問類字段(static字段,或者稱為類變量)和實例字段(非static字段,或者稱為實例變量)的指令:getfield、putfield、getstatic、putstatic。
    • 把一個數(shù)組元素加載到操作數(shù)棧的指令:baload、caload、saload、iaload、laload、faload、daload、aaload。
    • 將一個操作數(shù)棧的值存儲到數(shù)組元素中的指令:bastore、castore、sastore、iastore、fastore、dastore、aastore。
    • 取數(shù)組長度的指令:arraylength。
    • 檢查類實例類型的指令:instanceof、checkcast。

6.4.6 操作數(shù)棧管理指令

  1. 如同操作一個普通數(shù)據(jù)結(jié)構(gòu)中的堆棧那樣,Java虛擬機提供了一些用于直接操作操作數(shù)棧的指令,包括:
    • 將操作數(shù)棧的棧頂一個或兩個元素出棧:pop、pop2。
    • 復制棧頂一個或兩個數(shù)值并將復制值或雙份的復制值重新壓入棧頂:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2。
    • 將棧最頂端的兩個數(shù)值互換:swap。

6.4.7 控制轉(zhuǎn)移指令

  1. 控制轉(zhuǎn)移指令可以讓 Java 虛擬機有條件或無條件地從指定的位置指令而不是控制轉(zhuǎn)移指令的下一條指令繼續(xù)執(zhí)行程序,從概念模型上理解,可以認為控制轉(zhuǎn)移指令就是在有條件或無條件地修改PC寄存器的值??刂妻D(zhuǎn)移指令如下:

    • 條件分支:ifeq、int、ife、ifme、ifgt、ifge、ifmull、ifmonnul、if icmpeq、if icmpne、if icmplt、ificmpgt、if icmple、if_icmpge、if_acmpeq和if_acmpne。
    • 復合條件分支:tableswitch、lookupswitch??跓o條件分支:goto、goto w、jsr、jsr_w、ret。
    • 無條件分支:goto、goto w、jsr、jsr_w、ret。
      在Java虛擬機中有專門的指令集用來處理int和reference類型的條件分支比較操作,為了可以無須明顯標識一個實體值是否null,也有專門的指令用來檢測nul值。
  2. 對于boolean類型、byte類型、char 類型和short類型的條件分支比較操作,都是使用 int 類型的比較指令來完成,而對于long類型、float類型和double類型的條件分支比較操作,則會先執(zhí)行相應類型的比較運算指令(dcmpg、dcmpl、fcmpg、fcmpl、1cmp),運算指令會返回一個整型值到操作數(shù)棧中,隨后再執(zhí)行 int 類型的條件分支比較操作來完成整個分支跳轉(zhuǎn)。由于各種類型的比較最終都會轉(zhuǎn)化為 int 類型的比較操作,int類型比較是否方便完善就顯得尤為重要,所以Java虛擬機提供的int類型的條件分支指令是最為豐富和強大的。

6.4.8 方法調(diào)用和返回指令

  1. 方法調(diào)用僅列舉以下5條用于方法調(diào)用的指令:

    • invokevirtual 指令用于調(diào)用對象的實例方法,根據(jù)對象的實際類型進行分派(虛方法分派),這也是Java語言中最常見的方法分派方式。
    • invokeinterface 指令用于調(diào)用接口方法,它會在運行時搜索一個實現(xiàn)了這個接口方法的對象,找出適合的方法進行調(diào)用。
    • invokespecial 指令用于調(diào)用一些需要特殊處理的實例方法,包括實例初始化方法、私有方法和父類方法。
    • invokestatic 指令用于調(diào)用類方法(static方法)。
    • invokedynamic 指令用于在運行時動態(tài)解析出調(diào)用點限定符所引用的方法,并執(zhí)行該方法
  2. 前面4條調(diào)用指令的分派邏輯都固化在Java虛擬機內(nèi)部,而invokedynamic指令的分派邏輯是由用戶所設(shè)定的引導方法決定的。

  3. 方法調(diào)用指令與數(shù)據(jù)類型無關(guān),而方法返回指令是根據(jù)返回值的類型區(qū)分的,包括ireturn(當返回值是boolean、byte、char、short和int類型時使用)、lreturn、freturn、dreturn和areturn,另外還有一條return指令供聲明為void的方法、實例初始化方法以及類和接口的類初始化方法使用。

6.4.9異常處理指令

  1. 在 Java 程序中顯式拋出異常的操作(throw語句)都由athrow指令來實現(xiàn),除了用 throw 語句顯式拋出異常情況之外,Java虛擬機規(guī)范還規(guī)定了許多運行時異常會在其他 Java 虛擬機指令檢測到異常狀況時自動拋出。例如,當除數(shù)為零時,虛擬機會在idiv或ldiv 指令中拋出ArithmeticException異常。而在Java虛擬機中,處理異常(catch語句)不是由字節(jié)碼指令來實現(xiàn)的,而是采用異常表來完成的。

6.4.10 同步指令

  1. Java虛擬機可以支持方法級的同步和方法內(nèi)部一段指令序列的同步,這兩種同步結(jié)構(gòu)都是使用管程(Monitor)來支持的。
  2. 方法級的同步是隱式的,即無須通過字節(jié)碼指令來控制,它實現(xiàn)在方法調(diào)用和返回操作之中。虛擬機可以從方法常量池的方法表結(jié)構(gòu)中的 ACC_SYNCHRONIZED 訪問標志得知一個方法是否聲明為同步方法。當方法調(diào)用時,調(diào)用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程就要求先成功持有管程,然后才能執(zhí)行方法,最后當方法完成(無論是正常完成還是非正常完成)時釋放管程。
  3. 同步一段指令集序列通常是由 Java語言中的 synchronized語句塊來表示的,Java虛擬機的指令集中有monitorenter 和 monitorexit 兩條指令來支持synchronized關(guān)鍵字的語義,正確實現(xiàn)synchronized關(guān)鍵字需要 Javac 編譯器與 Java 虛擬機兩者共同協(xié)作支持

6.5 公有設(shè)計和私有實現(xiàn)

  1. Java虛擬機規(guī)范描繪了 Java 虛擬機應有的共同程序存儲格式:Class文件格式以及字節(jié)碼指令集。這些內(nèi)容與硬件、操作系統(tǒng)及具體的 Java 虛擬機實現(xiàn)之間是完全獨立的,虛擬機實現(xiàn)者可能更愿意把它們看做是程序在各種 Java 平臺實現(xiàn)之間互相安全地交互的手段。

  2. 理解公有設(shè)計與私有實現(xiàn)之間的分界線是非常有必要的,Java虛擬機實現(xiàn)必須能夠讀取Class文件并精確實現(xiàn)包含在其中的 Java 虛擬機代碼的語義

  3. 一個優(yōu)秀的虛擬機實現(xiàn),在滿足虛擬機規(guī)范的約束下對具體實現(xiàn)做出修改和優(yōu)化也是完全可行的,并且虛拱機規(guī)范中明確鼓勵實現(xiàn)者這樣做。只要優(yōu)化后Class文件依然可以被正確讀取,并且包含在其中的語義能得到完整的保持,那實現(xiàn)者就可以選擇任何方式去實現(xiàn)這些語義。

  4. 虛擬機實現(xiàn)者可以使用這種伸縮性來讓 Java 虛擬機獲得更高的性能、更低的內(nèi)存消耗或者更好的可移植性,選擇哪種特性取決于 Java 虛擬機實現(xiàn)的目標和關(guān)注點是什么。虛擬機實現(xiàn)的方式主要有以下兩種:

    • 將輸入的 Java 虛擬機代碼在加載或執(zhí)行時翻譯成另外一種虛擬機的指令集。
    • 將輸入的 Java 虛擬機代碼在加載或執(zhí)行時翻譯成宿主CPU的本地指令集(即 JIT-代碼生成技術(shù))。
  5. 精確定義的虛擬機和目標文件格式不應當對虛擬機實現(xiàn)者的創(chuàng)造性產(chǎn)生太多的限制,Java虛擬機應被設(shè)計成可以允許有眾多不同的實現(xiàn),并且各種實現(xiàn)可以在保持兼容性的同時提供不同的、新的、有趣的解決方案。

6.6 Class文件結(jié)構(gòu)的發(fā)展

  1. Class文件結(jié)構(gòu)自 Java 虛擬機規(guī)范第1版訂立以來,已經(jīng)有十多年的歷史。這十多年間,Class文件結(jié)構(gòu)一直處于比較穩(wěn)定的狀態(tài),Class文件的主體結(jié)構(gòu)、字節(jié)碼指令的語義和數(shù)量幾乎沒有出現(xiàn)過變動,所有對Class文件格式的改進,都集中在向訪問標志、屬性表這些在設(shè)計上就可擴展的數(shù)據(jù)結(jié)構(gòu)中添加內(nèi)容。

  2. 如果以《Java虛擬機規(guī)范(第2版)》為基準進行比較的話,那么在后續(xù)Class文件格式的發(fā)展過程中:

    • 訪問標志里新加入了ACC_SYNTHETIC、ACC_ANNOTATION、ACC_ENUM、ACC_BRIDGE、ACC_VARARGS共5個標志。
    • 屬性表集合中,在 JDK1.5 到 JDK1.7 版本之間一共增加了12項新的屬性,這些屬性大部分用于支持 Java 中許多新出現(xiàn)的語言特性,如枚舉、變長參數(shù)、泛型、動態(tài)注解等。還有一些是為了支持性能改進和調(diào)試信息。
  3. Class文件格式所具備的平臺中立(不依賴于特定硬件及操作系統(tǒng))、緊湊、穩(wěn)定和可擴展的特點,是 Java 技術(shù)體系實現(xiàn)平臺無關(guān)、語言無關(guān)兩項特性的重要支柱。

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

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

  • 1.概述 write one, run everywhere。 2. 無關(guān)性的基石 實現(xiàn)語言無關(guān)性的基礎(chǔ)是虛擬機和...
    過來摸摸頭丶閱讀 939評論 0 1
  • 類文件結(jié)構(gòu) 各種不同平臺的虛擬機與所有平臺都統(tǒng)一使用的程序存儲格式— 字節(jié)碼( ByteCode ) 是構(gòu)成平臺無...
    好好學習Sun閱讀 663評論 0 0
  • 在說道 JVM 虛擬機的時候,很多人都會想到 Java 語言,誠然,Java 語言和 JVM 虛擬機息息相關(guān),但是...
    lijiankun24閱讀 12,120評論 9 31
  • 在一陣慌亂之后,刑警們回頭看到兩只女鬼飄蕩著,奔下后山去。 刑警們頓時鎮(zhèn)定下來,從新回到山頂上,追擊的跟著沖下山背...
    繞飛閱讀 281評論 0 2
  • 因為項目原因,需要對影視這個行業(yè)熟悉,故有此文。 決定影視劇產(chǎn)品票房收入與收視率: 選題、故事敘事的角度、主創(chuàng)陣容...
    徐薇薇閱讀 2,491評論 1 4

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