Java虛擬機器(四):Class檔案結構及位元組碼指令

Java虛擬機器(四):Class檔案結構及位元組碼指令
一、Class檔案結構
    Java class檔案是對Java程式二進位制檔案格式的精確定義。每一個Java class檔案都對一個Java類或者Java介面作出了全面描述。一個class檔案中只能包含一個類或者介面。

1、平臺無關性
    儘管class檔案與Java語言結構相關,但它並不一定必須與Java語言相關。如下圖,可以使用其他語言來編寫程式,然後將其編譯為class檔案,或者把Java程式編譯為另一種不同的二進位制檔案格式。


                          

 
                     (注:如Scala、Groovy、JRuby等基於JVM的語言)
 
    Java class檔案是8位位元組的二進位制流。資料項按順序儲存在class檔案中,相鄰的項之間沒有任何間隔,這樣可以使class檔案緊湊。佔據多個位元組空間的項按照高位在前的順序分為幾個連續的位元組存放。
    在class檔案中,可變長度項的大小和長度位於其實際資料之前。這個特性使得class檔案流可以從頭到尾被順序解析,首先讀出項的大小,然後讀出項的資料。
 

2、class檔案的內容

    Java class檔案中包含了Java虛擬機器所需知道的、關於類或介面的所有資訊。

       class檔案“基本型別”

類 型

描 述

u1

1個位元組,無符號型別

u2

2個位元組,無符號型別

u4

4個位元組,無符號型別

u8

8個位元組,無符號型別

 

    可變長度的ClassFile表中的項,按照它們在class檔案中出現的順序列出了主要部分。

ClassFile表的格式

類 型

名 稱

數 量

u4

magic

1

u2

minor_version

1

u2

major_version

1

u2

constant_pool_count

1

cp_info

constant_pool

constant_pool_count-1

u2

access_flags

1

u2

this_class

1

u2

super_class

1

u2

interfaces_count

1

u2

interfaces

interfaces_count

u2

fields_count

1

field_info

fields

fields_count

u2

methods_count

1

method_info

methods

methods_count

u2

attributes_count

1

attribute_info

attributes

attributes_count

Class檔案組織結果:

 
1)magic(魔數)
    每個Java class檔案的前4個位元組被稱為它的魔數(magic number):0xCAFEBABE。魔數的作用在於,可以輕鬆地分辨出Java class檔案和非Java class檔案。
 
2)minor_version和major_version
    class檔案的下面4個位元組包含了主、次版本號。對於Java虛擬機器來說,版本號確定了特定的class檔案格式,通常只有給定主版本號和一系列次版本號後,Java虛擬機器才能夠讀取class檔案。
 
3)constant_pool_count和constant_pool
    魔數和版本號後面的是常量池。常量池包含了與檔案中類和介面相關的常量。常量池中儲存了諸如文字字串、final變數值、類名和方法名的常量。Java虛擬機器把常量池組織為入口列表的形式。在實際列表constant_pool之前,是入口在列表中的計數constant_pool_count。
    每個常量池入口都從一個長度為一個位元組的標誌開始,這個標誌指出了列表中該位置的常量型別。
    每一個標誌都有一個相對應的表,表名通過在標誌名後加上“_info”字尾來產生。

常量池標誌

入 口 類 型

標 志 值

描 述

CONSTANT_Utf8

1

UTF-8編碼的Unicode字串

CONSTANT_Integer

3

int型別字面值

CONSTANT_Float

4

float型別字面值

CONSTANT_Long

5

long型別字面值

CONSTANT_Double

6

double型別字面值

CONSTANT_Class

7

對一個類或介面的符號引用

CONSTANT_String

8

String型別字面值

CONSTANT_Fieldref

9

對一個欄位的符號引用

CONSTANT_Methodref

10

對一個類中宣告的方法的符號引用

CONSTANT_InterfaceMethodref

11

對一個介面中宣告的方法的符號引用

CONSTANT_NameAndType

12

對一個欄位或方法的部分符號引用

 

    除了字面常量(或者說直接量)值以外,常量池還可以容納下面幾種符號引用:
  • 類和介面的全限定名
  • 欄位的名稱和描述符
  • 方法的名稱和描述符
4)access_flags
    緊接常量池後的兩個位元組稱為access_flags,它展示了檔案中定義的類或介面的幾段資訊。

access_flags項的標誌位

標 志 名

設定後的含義

設 置 者

ACC_PUBLIC

0x0001

public型別

類和介面

ACC_FINAL

0x0010

類為final型別

只有類

ACC_SUPER

0x0020

使用新型的invokespecial語義

類和介面

ACC_INTERFACE

0x0200

介面型別,不是類型別

所有的介面,沒有類

ACC_ABSTRACT

0x0400

abstract型別

所有的介面,部分類

    在access_flags中所有未使用的位都必須由編譯器置0,而且Java虛擬機器必須忽略它。
 
5)this_class
    接下來的兩個位元組為this_class項,它是一個對常量池的索引。在this_class位置的常量池入口必須為CONSTANT_Class_info表。該表由兩個部分組成——標籤和name_index。標籤部分是一個具有CONSTANT_Class值的常量,在name_index位置的常量池入口為一個包含了類或介面全限定名的CONSTANT_Utf8_info表。




 
6)super_class
    緊接在this_class之後的是super_class項,它是一個兩個位元組的常量池索引。在super_class位置的常量池入口是一個指向該類超類全限定名的CONSTANT_Class_info入口。
 
7)interfaces_count和interfaces
    緊接著super_class是interfaces_count。此項的含義為:在檔案中由該類直接實現或者由介面所擴充套件的父介面的數量。在這個計數的後面,是名為interfaces的陣列,它包含了對每個由該類或者介面直接實現(注:只包含直接出現在implements、extends子句中的父介面)的父介面的常量池索引。
 
8)fields_count和fields
    緊接在interfaces後面的是對在該類或者介面中所宣告的欄位的描述。首先是名為fields_count的計數,它是類變數和例項變數的欄位的數量總和。在這個計數後面的是不同長度的field_info表的序列(fields_count指出了序列中有多少個field_info表)。在fields列表中,不列出從超類或者父介面繼承而來的欄位。
 
9)methods_count和methods
    緊接著fields後面的是對在該類或者介面中所宣告的方法的描述。只包括在該類或者介面中顯式定義的方法。
 
10)attributes_count和attributes
    class檔案中最後的部分是屬性(attribute),它給出了在該檔案中類或者介面所定義的屬性的基本資訊。
 
  • 特殊字串
    常量池中容納的符號引用包括三種特殊的字串:全限定名、簡單名稱和描述符。
 
  • 全限定名
    當常量池入口指向類或者介面時,它們給出該類或者介面的全限定名。在class檔案中,全限定名中的點用斜線取代了。
 
  • 簡單名稱
    欄位名和方法名以簡單名稱(非全限定名)形式出現在常量池入口中。
 
  • 描述符
    欄位的描述符給出了欄位的型別;方法描述符給出了方法的返回值和方法引數的數量、型別以及順序。
    欄位和方法的描述符由如下所示的上下文無關語法定義。該語法中非終結符號用斜體字標出,如FieldType;終結符號使用等寬度字型標出,如B或V;星號代表緊接在它前面的符號(中間沒有空格)將會出現0次或者多次。
 
FieldDescriptor: FieldType
ComponentType:FieldType
FieldType: BaseType、 ObjectType、 ArrayType
BaseType: B、C、 D、F、 I、 J、S、Z
ObjectType:、L<classname>;
ArrayType:、[ComponentType
MethodDescriptor:、(ParameterDescriptor*)ReturnDescriptor
ParameterDescriptor:、 FieldType
ReturnDescriptor:、 FieldType、 V
 
V        表示方法返回值為void型別
L和;      物件型別終結符
[         陣列型別終結符
(和)      方法描述符終結符
 

基本型別終結符

終 結 符

類 型

B

byte

C

char

D

double

F

float

I

int

J

long

S

short

Z

boolean


欄位描述符示例

描 述 符

字 段 聲 明

I

int i;

[[J

long[][] windingRoad;

[Ljava/lang/Object;

java.lang.Object[] stuff;

Ljava/util/Hashtable;

java.util.Hashtable ht;

[[[Z

boolean[][][] isReady;


方法描述符示例

描 述 符

方 法 聲 明

()I

int getSize();

()Ljava/lang/String;

String toString();

([Ljava/lang/String;)V

void main(String[] args);

()V

void wait();

(JI)V

void wait(long timeout, int nanos);

(ZILjava/lang/String;II)Z

boolean regionMatches(boolean ignoreCase, int toOffset, String other, int offset, int len);

([BII)I

int read(byte[] b, int off, int len);


3、常量池
    常量池是一個可變長度cp_info表的有序序列。cp_info表一共有11種型別。

cp_info表的通常形式

類 型

名 稱

數 量

描 述

u1

tag

1

表的型別和格式

u1

info

根據tag值決定



1)CONSTANT_Utf8_info表
    可變長度的CONSTANT_Utf8_info表使用一種UTF-8格式的變體來儲存一個常量字串。這種型別的表可以儲存多種字串,包括:
    – 文字字串,如String物件。
    – 被定義的類和介面的全限定名。
    – 被定義的類的超類(如果有的話)的全限定名。
    – 被定義的類和介面的父介面的全限定名。
    – 由類或者介面宣告的任意欄位的簡單名稱和描述符。
    – 由類或者介面宣告的任意方法的簡單名稱和描述符。
    – 任何引用的類和介面的全限定名。
    – 任何引用的欄位的簡單名稱和描述符。
    – 任何引用的方法的簡單名稱和描述符。
    – 與屬性相關的字串。

CONSTANT_Utf8_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_Utf8(1)

u2

length

1

bytes項的長度(位元組數)

u1

bytes

length

按照變體UTF-8格式儲存的字串中的字元


2)CONSTANT_Integer_info表的格式

CONSTANT_Integer_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_Integer(3)

u4

bytes

1

按照高位在前的格式儲存int型別值


3)CONSTANT_Float_info表的格式

CONSTANT_Float_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_Float(4)

u4

bytes

1

按照高位在前的格式儲存float型別值


4)CONSTANT_Long_info表的格式

CONSTANT_Long_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_Long(5)

u8

bytes

1

按照高位在前的格式儲存long型別值


5)CONSTANT_Double_info表的格式

CONSTANT_Double_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_Double(6)

u8

bytes

1

按照高位在前的格式儲存double型別值


6)CONSTANT_Class_info表的格式

CONSTANT_Class_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_Class(7)

u2

name_index

1

包含類或者介面全限定名的CONSTANT_Utf8_info表的索引


7)CONSTANT_String_info表的格式

CONSTANT_String_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_String(8)

u2

string_index

1

包含文字字串值的CONSTANT_Utf8_info表的索引


8)CONSTANT_Fieldref_info表的格式

CONSTANT_Fieldref_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_Fieldref(9)

u2

class_index

1

宣告被引用欄位的類或者介面的CONSTANT_Class_info入口的索引

u2

name_and_type_index

1

提供了CONSTANT_NameAndType_info入口的索引,該入口提供了欄位的簡單名稱以及描述符


9)CONSTANT_Methodref_info表的格式

CONSTANT_Methodref_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_Methodref(10)

u2

class_index

1

宣告被引用方法的類的CONSTANT_Class_info入口的索引

u2

name_and_type_index

1

提供了CONSTANT_NameAndType_info入口的索引,該入口提供了方法的簡單名稱以及描述符


10)CONSTANT_InterfaceMethodref_info表的格式

CONSTANT_InterfaceMethodref_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_InterfaceMethodref(11)

u2

class_index

1

宣告被引用方法的介面的CONSTANT_Class_info入口的索引

u2

name_and_type_index

1

提供了CONSTANT_NameAndType_info入口的索引,該入口提供了方法的簡單名稱以及描述符


11)CONSTANT_NameAndType_info表的格式

CONSTANT_NameAndType_info表的格式

類 型

名 稱

數 量

描 述

u1

tag

1

值為CONSTANT_NameAndType(12)

u2

name_index

1

給出了CONSTANT_Utf8_info入口的索引,該入口給出了欄位或者方法的名稱

u2

descriptor_index

1

提供了CONSTANT_Utf8_info入口的索引,該入口提供了欄位或者方法的描述符

 
4、欄位
    在類或者介面中宣告的每一個欄位(類變數或者例項變數)都由class檔案中的一個名為field_info的可變長度的表進行描述。

field_info表的格式

類 型

名 稱

數 量

描 述

u2

access_flags

1

見下方的表

u2

name_index

1

提供了給出欄位簡單名稱(不是全限定名)的CONSTANT_Utf8_info入口的索引

u2

descriptor_index

1

提供了給出欄位描述符的CONSTANT_Utf8_info入口的索引

u2

atrributes_count

1

attributes_count指出列表中attribute_info表的數量

attribute_info

atrributes

atrributes_count

由多個attribute_info表組成的列表


field_info表中access_flags項的標誌

標 志 名 稱

設 定 含 義

設 定 者

ACC_PUBLIC

0x0001

欄位設為public

類和介面

ACC_PRIVATE

0x0002

欄位設為private

只有類

ACC_PROTECTED

0x0004

欄位設為protected

只有類

ACC_STATIC

0x0008

欄位設為static

類和介面

ACC_FINAL

0x0010

欄位設為final

類和介面

ACC_VOLATILE

0x0040

欄位設為volatile

只有類

ACC_TRANSIENT

0x0080

欄位設為transient

只有類

 
    類(不包括介面)中宣告的欄位,只能擁有ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED這三個標誌中的一個。ACC_FINAL和ACC_VOLATILE不能同時設定。所有介面中宣告的欄位必須有且只能有ACC_PUBLIC、ACC_STATIC和ACC_FINAL這三種標誌。
 
5、方法
    在class檔案中,每個在類和介面中宣告的方法,或者由編譯器產生的方法,都由一個可變長度的method_info表來描述。

method_info表的格式

類 型

名 稱

數 量

描 述

u2

access_flags

1

見下方的表

u2

name_index

1

提供了給出方法簡單名稱(不是全限定名)的CONSTANT_Utf8_info入口的索引

u2

descriptor_index

1

提供了給出方法描述符的CONSTANT_Utf8_info入口的索引

u2

atrributes_count

1

attributes_count指出列表中attribute_info表的數量

attribute_info

atrributes

atrributes_count

由多個attribute_info表組成的列表


method_info表中access_flags項的標誌

標 志 名 稱

設 定 含 義

設 定 者

ACC_PUBLIC

0x0001

方法設為public

類和所有的介面方法

ACC_PRIVATE

0x0002

方法設為private

只有類

ACC_PROTECTED

0x0004

方法設為protected

只有類

ACC_STATIC

0x0008

方法設為static

只有類

ACC_FINAL

0x0010

方法設為final

只有類

ACC_SYNCHRONIZED

0x0020

方法設為synchronized

只有類

ACC_NATIVE

0x0100

方法設為native

只有類

ACC_ABSTRACT

0x0400

方法設為abstract

類和所有的介面方法

ACC_STRICT

0x0800

方法設為strictFP

類和介面的<clinit>方法


    屬性在Java class檔案中多處出現。它們可以出現在ClassFile、field_info、method_info和Code_attribute表中。Code_attribute表本身即為一個屬性。
    Java虛擬機器規範定義了9種屬性。為了正確地解釋Java class檔案,所有Java虛擬機器實現都必須能夠識別下列三種屬性:Code,ConstantValue和Exception。為了正確地實現Java和Java 2平臺類庫,虛擬機器實現必須能夠識別InnerClasses和Synthetic屬性,但可以自主選擇究竟是識別還是忽略其他一些預定義的屬性。

由規範定義的attribute_info表的型別

名 稱

使 用 者

描 述

Code

method_info

方法的位元組碼和其他資料

ConstantValue

field_info

final變數的值

Deprecated

field_info、method_info

欄位或者方法被禁用的指示符

Exceptions

method_info

方法可能丟擲的可被檢測的異常

InnerClasses

ClassFile

內部、外部類的列表

LineNumberTable

Code_attribute

方法的行號與位元組碼的對映

LocalVariableTable

Code_attribute

方法的區域性變數的描述

SourceFile

ClassFile

原始檔名

Synthetic

field_info、method_info

編譯器產生的欄位或者方法的指示符


二、Java位元組碼指令               
1、
載入與儲存指令

載入和儲存指令用於將資料從棧幀的區域性變數表和運算元棧之間來回傳輸:

    – 將一個區域性變數載入到操作棧的指令包括有: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_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>
    – 擴充區域性變數表的訪問索引的指令:wide

        訪問物件的欄位或陣列元素的指令也同樣會與運算元棧傳輸資料。

        上面所列舉的指令助記符中,有一部分是以尖括號結尾的(例如 iload_<n>),這些指令助記符實際上是代表了一組指令(例如 iload_<n>,它代表了 iload_0、iload_1、iload_2 和 iload_3 這幾條指令)。這幾組指令都是某個帶有一個運算元的通用指令(例如
iload)的特殊形式,對於這若干組特殊指令來說,它們表面上沒有運算元,不需要進行取運算元的動作,但運算元都是在指令中隱含的。除此之外,他們的語義與原生的通用指令完全一致(例如 iload_0 的語義與運算元為 0 時的 iload 指令語義完全一致)。在尖括號之間的字母制定了指令隱含運算元的資料型別,<i>代表是 int 形資料,<l>代表 long 型,<f>代表 float 型,<d>代表 double型。在操作 byte、char 和 short 型別資料時,也用 int 型別表示。

這種指令表示方法,在整個《Java 虛擬機器規範》之中都是通用的。

2、運算指令

        算術指令用於對兩個運算元棧上的值進行某種特定運算,並把結果重新存入到操作棧頂。大體上運算指令可以分為兩種:對整型資料進行運算的指令與對浮點型資料進行運算的指令,無論是那種算術指令,都是使用 Java 虛擬機器的數字型別的。資料沒有直接支援
byte、short、char 和 boolean 型別的算術指令,對於這些資料的運算,都是使用操作 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
3、型別轉換指令
    窄化型別轉換(Narrowing Numeric Conversions)指令包括有:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l
和 d2f。窄化型別轉換可能會導致轉換結果產生不同的正負號、不同的數量級,轉換過程很可能會導致數值丟失精度。
4、物件建立與操作指令

    雖然類例項和陣列都是物件,但 Java 虛擬機器對類例項和陣列的建立與操作使用了不同的位元組碼指令:

    – 建立類例項的指令: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、checkcas

5、運算元棧管理指令

    Java 虛擬機器提供了一些用於直接操作運算元棧的指令,包括:pop、pop2、dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2 和 swap。

6、控制轉移指令

    控制轉移指令可以讓 Java 虛擬機器有條件或無條件地從指定指令而不是控制轉移指令的下一條指令繼續執行程式。控制轉移指令包括有:

    – 條件分支: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 方法)。

    而方法返回指令則是根據返回值的型別區分的,包括有 ireturn(當返回值是 boolean、byte、char、short 和 int 型別時使用)、lreturn、freturn、dreturn 和 areturn,另外還有一條 return
指令供宣告為 void 的方法、例項初始化方法、類和介面的類初始化方法使用。

8、丟擲異常

    在程式中顯式丟擲異常的操作會由 athrow 指令實現,除了這種情況,還有別的異常會在其它 Java 虛擬機器指令檢測到異常狀況時由虛擬機器自動丟擲。

9、同步指令

    同步一段指令集序列通常是由 Java 語言中的 synchronized 塊來表示的,Java 虛擬機器的指令集中有 monitorenter 和 monitorexit
兩條指令來支援 synchronized 關鍵字的語義,正確實現 synchronized 關鍵字需要編譯器與 Java 虛擬機器兩者協作支援。