JVM 之 Class文件結構

JVM 之 Class文件結構

本文寫做目的:html

1)爲了加深本身學習的理解,2)幫助正在學習研究JVM的同仁,3)與任何熱愛技術的達人交流經驗,提高本身java

以此爲本,文章會盡可能寫的簡潔,儘可能保證理解的正確性,若有任何理解不到位或錯誤的地方,但願朋友們及時指出,嚴厲拍磚。編程


開始以前咱們須要先了解一些基本的概念,這些概念是學習整個JVM原理的基礎。bootstrap

1)JVM虛擬機規範主要規範了Class文件結構,虛擬機內存結構,虛擬機加載,解析,執行Class文件的行爲方式,以及一系列的字節碼指令集。數組

2)Class文件理論上說是一種數據結構,該數據結構有着嚴格的格式規範,該規範在字節粒度上規定了組成該數據結構的格式標準。數據結構

3)Class文件本質上是一組二進制字節流,是被JVM解析執行的數據源,每一個字節都有着不一樣的含義,可能表示字符,數字,也可能表示執行某種操做的一個字節碼指令。oracle

4)JVM (Java 虛擬機)是解析執行Class文件的核心引擎,是整個Java系統的運行時環境,是跨平臺的基石。app

5)咱們的Java代碼須要被編譯器編譯成完整,正確的Class文件才能被JVM正確的執行。jvm

6)編譯器並不是JVM的一部分,不一樣的語言能夠提供不一樣的編譯器,其做用是將該語言的代碼編譯爲正確的Class文件,如Scala,JRuby等等。編程語言

7)JVM是徹底開放的跨平臺的,只要你有能力你能夠按照JVM虛擬機規範編寫本身的編程語言。

8)JVM 使得Java的跨平臺成爲可能,那麼Class文件結構規範則使得更多的編程語言運行在JVM上成爲可能。


既然Class文件是一種數據結構,那麼究竟是什麼樣的數據結構呢?一般計算機中的文件都包含元數據和實體數據兩部分,元數據用來存儲該文件的描述信息,實體數據來存放用於表達文件真實內容的數據。固然Class文件也不例外,咱們爲了便於理解,也將class文件的結構分爲兩大部分:元數據和實體數據(注:非規範定義,只是爲了方便理解進行的自定義)。

元數據:包含了Class文件的魔術數(標識符)和版本信息。

實體數據:包含了常量池,字段描述,方法描述,屬性描述等用於表述該類行爲的具體信息。

元數據咱們很少贅述對咱們後面的分析沒多大關係,下面主要分析下實體數據。

一,結構概覽

無論是元數據仍是實體數據他們都以字節爲單位按順序緊湊的排列在class文件中,沒有任何多餘空間。爲了描述class文件結構,虛擬機規範定義了u1, u2, u4, u8四種基本數據結構和一種由這四種基本數據結構組成的複雜數據結構-表(一般以info結尾表示),這四種基本數據結構分別表示一個字節,兩個字節,四個字節,八個字節。基於此咱們即可以清晰的瞭解class文件結構的整體輪廓了(C語言語法,其中常量表,變量表,方法表,屬性表都有一到多個,所以定義爲數組),如<<代碼一>>

class-file {
    u4             magic;//魔術數
    u2             minor_version;//小版本號
    u2             major_version;//大版本號
    u2             constant_pool_count;//常量池中常量個數+1
    cp_info        constant_pool[constant_pool_count-1];//常量池
    u2             access_flags;//類的訪問控制符標識(public,static,final,abstract等)
    u2             this_class;//該類的描述(值爲對常量池的引用,引用的值爲CONSTANT_Class_info)
    u2             super_class;//父類的描述(值爲對常量池的引用,引用的值爲CONSTANT_Class_info)
    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;//屬性數,包括InnerClasses,EnclosingMethod,SourceFile等
    attribute_info attributes[attributes_count];//屬性表集合
}

注:表自己是一種複雜結構,包含多個字節,Class文件機構中定義了四種表結構,1)常量表2)變量表3)方法表4)屬性表。因爲每種表都有一到多個,因此在<<代碼一>>中能夠看出他們都是在數組中的。

接下來咱們按順序研究下每一個部分的具體含義!


二,常量池

Class文件中的常量池大小(constant_pool_count)由第九,第十兩個字節(前八個字節用來描述版本信息)決定,咱們知道兩個字節最大能夠表示65535,這也就代表一個Class文件中最多能夠具有65535個常量,包括數值,字符串,類名,方法名,變量名等等。接下來的constant_pool_count個字節就用來描述全部的常量了。爲了能表示各類可能類型的值,常量在Class文件中被定義成一種複雜結構:如<<代碼二>>

cp_info {//常量表的通用結構,不一樣類型的常量表以此爲基礎各不相同
    u1 tag;
    u1 info[];
}

能夠看出,常量的第一個字節表示了該常量的類型。

注:constant_pool_count的值爲常量個數+1,而且常量池常量的索引從1開始!!!

下面爲各種型的映射表:<<表一>>

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

由上表能夠看出,目前爲止JVM 一共定義了14種類型的常量。

每一個常量表的第一個字節代表了常量的類型,那麼剩餘的值則根據類型的不一樣代表了不一樣的含義,多是一個直接值,也多是一個對另外一個常量的引用,那麼不一樣類型的常量表定義以下:



<<代碼三>>

CONSTANT_Utf8_info {//表示Utf8的常量
    u1 tag;// 值爲 1
    u2 length;
    u1 bytes[length];
}

Constant_Utf8_info常量用來表示一個utf8字符串,其長度爲可變長度,第一個字節的值固定爲1(<<表一>>中Constant_Utf8(1)),後面兩個字節表示了個字符串的字節長度length(而不是字符串的長度),而後後面緊跟着的length個字節就是字符串的字節碼了。該常量被引用的頻率頗高,如類名,方法名等常量都引用它。


<<代碼四>>

CONSTANT_Class_info {//表示類或接口的常量
    u1 tag;//值爲 7
    u2 name_index;//值爲對Constant_Utf8_info常量的引用
}

Constant_Class_info常量用來表示一個類或者接口,一共包含三個字節,第一個字節的值固定爲 7(<<表一>>中Constant_Class(7)),後兩個字節的值爲對常量池中另外一個常量(Constant_Utf8_info)的索引,該Constant_Utf8_info常量的值應爲JVM的內部二進制類或接口名(binary class or interface name下文詳解)。


<<代碼五>>

CONSTANT_Fieldref_info {//表示變量的常量
    u1 tag;//值爲 9 <<表一>>中CONSTANT_Fieldref(9)
    u2 class_index; //值爲對Constant_Class_info常量的索引
    u2 name_and_type_index; //值爲CONSTANT_NameAndType_info常量的索引
}

CONSTANT_Methodref_info {//表示方法的常量
    u1 tag;//值爲 10 <<表一>>中CONSTANT_Methodref(10)
    u2 class_index; //值爲對Constant_Class_info常量的索引
    u2 name_and_type_index; //值爲CONSTANT_NameAndType_info常量的索引
}

CONSTANT_InterfaceMethodref_info {//表示接口方法的常量
    u1 tag;//值爲 11 <<表一>>中CONSTANT_InterfaceMethodref(11)
    u2 class_index; //值爲對Constant_Class_info常量的索引
    u2 name_and_type_index; //值爲CONSTANT_NameAndType_info常量的索引
}

代碼五中的三個很是類似的常量結構分別表示了變量,方法,接口方法,其中各個值得含義和大小已在註釋中說明。其實很容易理解,好比一個方法,它屬於哪一個類(class_index),它的名字和類型(name_and_type)。


<<代碼六>>

CONSTANT_String_info {//表示字符串的常量
    u1 tag;//值爲 8 <<表一>>中CONSTANT_String (8)
    u2 string_index;
}

Constant_String_info表示了一個String類型的常量實例。一共佔用三個字節,第一個字節固定爲8,後兩個字節爲對Constant_Utf8_info常量的索引。


<<代碼七>>

CONSTANT_Integer_info {//表示int類型數值的常量
    u1 tag;//值爲 3 <<表一>>中CONSTANT_Integer (3)
    u4 bytes;
}

CONSTANT_Float_info {//表示float類型數值的常量
    u1 tag;//值爲 4 <<表一>>中CONSTANT_Float (4)
    u4 bytes;
}

CONSTANT_Long_info {//表示long類型數值的常量
    u1 tag;//值爲 5 <<表一>>中CONSTANT_Long (5)
    u4 high_bytes;
    u4 low_bytes;
}

CONSTANT_Double_info {//表示double類型數值的常量
    u1 tag;//值爲 6 <<表一>>中CONSTANT_Double (6)
    u4 high_bytes;
    u4 low_bytes;
}

上面四個常量結構分別表示int, float, long,double類型的常量,比較直觀,很少贅述。


<<代碼八>>

CONSTANT_NameAndType_info {//用來表示變量,方法名字和類型的常量
    u1 tag;//值爲 12 <<表一>>中CONSTANT_NameAndType (12)
    u2 name_index;//值爲對Constant_Utf8_info常量的引用
    u2 descriptor_index;//值爲對Constant_Utf8_info常量的引用
}

Constant_NameAndType_info用來表示變量或者方法的名字和類型的常量,該常量不包含該變量或方法的所屬類或接口的引用,主要用來被Constant_Method_info,Constant_Field_info等常量使用。該常量一共包含五個字節,第一個固定爲12,第二三個的值爲對Constant_Utf8_info常量的引用,該Constant_Utf8_info常量的值應爲變量或方法的有效的非限定名( unqualified name 下文詳解),第四五的值爲對Constant_Utf8_info常量的引用,該Constant_Utf8_info常量的值應爲有效的變量或方法的描述符(field descriptor,method descriptor下文詳解)。


<<代碼九>>

CONSTANT_MethodHandle_info {//用來表示方法句柄的常量
    u1 tag;//值爲 15 <<表一>>中CONSTANT_MethodHandle (15)
    u1 reference_kind;//引用類型,值爲0-9,代表了該句柄的字節碼行爲
    u2 reference_index;//引用索引
}

Constant_MethodHandle_info常量用來表示方法句柄,第一個字節固定爲15,第二個字節的值爲0-9,分別代表了該句柄的不一樣字節碼行爲,其值的描述見<<表二>>,最後兩個字節爲對常量池中某常量的引用,但具體引用那種常量由reference_kind而定。

   a,若是reference_kind的值爲1 (REF_getField), 2 (REF_getStatic), 3 (REF_putField), 或 4 (REF_putStatic),那麼reference_index的值爲對Constant_Fieldref_info常量的引用,表示該句柄用於的變量。

   b,若是reference_kind的值爲5 (REF_invokeVirtual), 6 (REF_invokeStatic), 7 (REF_invokeSpecial), or 8 (REF_newInvokeSpecial),那麼reference_index的值爲對Constant_Methodref_info常量的引用,表示該句柄所用與的方法或構造器。

   c,若是reference_kind 的值是9(REF_invokeInterface)時,reference_index的值必須爲對Constant_InterfaceMethodref_info常量的引用,表示該句柄所用與的接口方法。

   d,若是reference_kind 的值是5 (REF_invokeVirtual), 6 (REF_invokeStatic), 7 (REF_invokeSpecial), or 9 (REF_invokeInterface)時,方法的名字必定不能夠爲<init>或<clinit>。

   e,若是reference_kind的值是8 (REF_newInvokeSpecial)時,方法的名字必須爲<init>。


<<表二>>

Kind Description Interpretation
1 REF_getField getfield C.f:T
2 REF_getStatic getstatic C.f:T
3 REF_putField putfield C.f:T
4 REF_putStatic putstatic C.f:T
5 REF_invokeVirtual invokevirtual C.m:(A*)T
6 REF_invokeStatic invokestatic C.m:(A*)T
7 REF_invokeSpecial invokespecial C.m:(A*)T
8 REF_newInvokeSpecial new C; dup; invokespecial C.<init>:(A*)void
9 REF_invokeInterface invokeinterface C.m:(A*)T


<<代碼十>>

CONSTANT_MethodType_info {//用來表示方法類型的常量
    u1 tag;//值爲 16,<<表一>>中CONSTANT_MethodType (16)
    u2 descriptor_index;//值爲對Constant_Utf8_info常量的索引,表示方法的描述符(下文詳解)
}

Constant_MethodType_info常量用來表示方法類型的常量,描述很直觀,很少贅述。


<<代碼十一>>

CONSTANT_InvokeDynamic_info {//該常量用來指定invokedynamic指令的引導方法。。。
    u1 tag;//值爲 18,<<表一>>中CONSTANT_InvokeDynamic (18) 
    u2 bootstrap_method_attr_index;//值爲對屬性表BootstrapMethods的有效引用
    u2 name_and_type_index;//值爲對Constant_NameAndType_info常量的引用
}

Constant_InvokeDynamic_info常量用來指定invokedynamic指令的引導方法,動態調用名稱,調用的參數和返回值hi,以及一些列可選的引導方法使用的叫作靜態參數的常量。


三,訪問標誌位

Class文件中緊跟在常量池後的訪問標誌位,一共佔用兩個字節,也就是十六個bit位,每一個bit位標記一種類的訪問修飾符,如final,abstract,public等,如今JVM已經使用了其中的八個,其他八個保留位將來使用,而且必須置零。八個標誌位映射以下表<<表三>>

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_FINAL 0x0010 Declared final; no subclasses allowed.
ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction.
ACC_INTERFACE 0x0200 Is an interface, not a class.
ACC_ABSTRACT 0x0400 Declared abstract; must not be instantiated.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
ACC_ANNOTATION 0x2000 Declared as an annotation type.
ACC_ENUM 0x4000 Declared as an enum type.

各個標誌位間有必定的約束條件,如ACC_ANNOTATION置位時,ACC_INTERFACE 必須置位等。


四,類/父類/接口的描述

Class文件中緊跟在訪問標誌位後的是this_class, super_class, interface_count, interfaces[],分別用來表示該類,該類的直接父類(是直接父類哦),實現的接口數量,以及接口信息等。

A,其中this_class用來表示該類的信息,其值爲對常量池中Constant_Class_info常量的引用。

B,super_class用來表示該類的直接父類父類信息,其值要麼是0要麼是對常量池中Constant_Class_info常量的引用。但有如下幾點須要注意:

1)若是其值爲對常量池Constant_Class_info 的引用,那麼被引用的類(直接父類)的ACC_FINAL訪問標誌位必須不能被置位。

2)若是其值爲0,那麼該類必須,必定是Object類

3)接口的super_class值必須是對常量池Constant_Class_info常量的引用,而且該常量表示的是Object類。

C,interface_count代表了該類實現接口的數量,而interfaces[]表,則代表了全部的實現接口。其中每個interface的值佔用兩個字節,總共佔用interface_count * 2個字節,都是對常量池Constant_Class_info 常量的引用。


五,變量表(字段表)

接下來緊跟在接口定義後面的是變量個數和變量表。該表結構用來描述類中的某個變量定義,不會同時有兩個名字和描述符都相同的變量。變量的結構描述結構以下<<代碼十二>>

field_info {
    u2             access_flags;//訪問標誌位,跟常量池後用來修飾類的access_flag做用和用法一致
    u2             name_index;//變量名索引,對常量池CONSTANT_Utf8_info常量的索引
    u2             descriptor_index;//變量描述符索引,對常量池CONSTANT_Utf8_info常量的索引
    u2             attributes_count;//屬性數量
    attribute_info attributes[attributes_count];//包含的屬性
}

上面的結構即是Class文件中用來描述某個變量(實例變量,類變量等)的定義。前三個u2字節分別代表了變量的訪問修飾符,名稱和描述符(下文詳解)。其中access_flag 映射以下表<<表四>>

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; usable only within the defining class.
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; never directly assigned to after object construction (JLS §17.5).
ACC_VOLATILE 0x0040 Declared volatile; cannot be cached.
ACC_TRANSIENT 0x0080 Declared transient; not written or read by a persistent object manager.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
ACC_ENUM 0x4000 Declared as an element of an enum.

名稱和描述符都是對常量池中Constant_Utf8_info常量的索引。以上三個U2能夠描述一個沒有初始值的變量定義了,如private static int i; 可是若是指定了private static int i = 1;那麼則會用到名爲ConstantValue的attribute_info結構,下文講解attribute_info時詳解。變量中的屬性除了ConstantValue外還可能含有Synthetic, Signature , Deprecated, RuntimeVisibleAnnotations  RuntimeInvisibleAnnotations 等屬性分別表示該變量是否爲編譯器合成的,變量的簽名,是否爲廢棄的,運行時可見註解,運行時不可見註解。


六,方法表

Class文件中跟在變量表後面的是方法個數和方法表,該表結構表示一個方法的定義,其中也會包括實例初始化方法(instance initialization method, <init>),類和接口初始化方法(class or interface initialization method, <clinit>)。方法表的描述以下<<代碼十三>>

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

該結構跟變量表的結構幾乎徹底相同,包括方法的訪問標誌位,名稱索引,描述符索引等。下表爲方法的訪問標誌位映射

<<表五>>

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class.
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; must not be overridden (§5.4.5).
ACC_SYNCHRONIZED 0x0020 Declared synchronized; invocation is wrapped by a monitor use.
ACC_BRIDGE 0x0040 A bridge method, generated by the compiler.
ACC_VARARGS 0x0080 Declared with variable number of arguments.
ACC_NATIVE 0x0100 Declared native; implemented in a language other than Java.
ACC_ABSTRACT 0x0400 Declared abstract; no implementation is provided.
ACC_STRICT 0x0800 Declared strictfp; floating-point mode is FP-strict.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.

不一樣的是,方法中包含的屬性種類跟變量中的屬性種類有所不一樣,其中可能包含Code  Exceptions Synthetic  Signature  Deprecated  RuntimeVisibleAnnotations  RuntimeInvisibleAnnotations RuntimeVisibleParameterAnnotations  RuntimeInvisibleParameterAnnotations and AnnotationDefault。其中Code屬性中包含了該方法的方法體(下文詳解)。


七,屬性表

到目前爲止咱們已經瞭解了字段表(變量表),方法表,常量池,訪問標誌位,類的繼承等幾乎所有類的信息在Class文件中的表達方式了。可是你確定發現,在方法表中,咱們只瞭解到了方法的名稱,描述符,訪問控制等信息,卻沒有方法體的詳細描述。不過咱們也提到了,方法的方法體是經過屬性表來記錄的。屬性表的結構將在本小結進行較爲詳細的講解。

你可能已經發現了,變量表(field_info)方法表(method_info)常量表(cp_info)等結構的頂級結構都是Class文件,換句話說,這些結構都是不可嵌套的。然而你也瞭解到了,咱們接下來說的屬性表結構倒是能夠嵌套的,它能夠存在在變量表,方法表結構以內,一樣也能夠有類級別的屬性表。

屬性表是一類至關靈活的結構,JVM定義了21種屬性定義,每一種屬性都有本身的結構和用途,有點相似常量池中的常量定義。21種屬性中咱們只挑選個別與與咱們關係比較密切的講解,其它屬性看官能夠用一樣的分析方法自行研究瞭解。


<<代碼十四>>

attribute_info {//屬性表的基本結構,不一樣類型的屬性表以此爲基礎各不相同
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

以上代碼描述了屬性表的結構定義,前兩個字節是對常量池中Constant_Utf8_info常量的索引,被索引的常量值爲該屬性的名字。中間四個字節是屬性內容的字節長度,算下4個字節能最多表示多大?2的32次方?不是,虛擬機規範中規定一個方法不可超過65535個字節。最後的attribute_length 個字節則是屬性的內容,如方法體字節碼指令集合。

前面說了目前JVM預約義了21種屬性,參照<<表六>>

Attribute
Java SE class file
ConstantValue
1.0.2 45.3
Code
1.0.2 45.3
StackMapTable
6 50.0
Exceptions
1.0.2 45.3
InnerClasses
1.1 45.3
EnclosingMethod
5.0 49.0
Synthetic
1.1 45.3
Signature
5.0 49.0
SourceFile
1.0.2 45.3
SourceDebugExtension
5.0 49.0
LineNumberTable
1.0.2 45.3
LocalVariableTable
1.0.2 45.3
LocalVariableTypeTable
5.0 49.0
Deprecated
1.1 45.3
RuntimeVisibleAnnotations
5.0 49.0
RuntimeInvisibleAnnotations
5.0 49.0
RuntimeVisibleParameterAnnotations
5.0 49.0
RuntimeInvisibleParameterAnnotations
5.0 49.0
AnnotationDefault
5.0 49.0
BootstrapMethods
7 51.0

上表能夠看出每種屬性的名字和初始版本信息,Java SE7中新加入了BootstrapMethods屬性,invokedynamic指令等實現動態語言的特性。

限於篇幅,咱們這裏只分析Code,ContantValue屬性。


一,ContantValue屬性

<<代碼十五>>定義了ContantValue屬性表的結構

ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}

能夠看出該屬性是定長的表結構,總共有8個字節大小。前面講過了前兩部分用來代表屬性的名字和大小,ContantValue屬性表中的name-index索引的常量值固定爲「ContantValue」。另外該屬性只會出如今變量表(field_info)中,用來表示該變量的值。

constantvalue_index也是對常量池中某常量的索引,其索引的常量類型根據變量的類型不一樣而不一樣,以下表<<表七>>

Field Type Entry Type
long CONSTANT_Long
float CONSTANT_Float
double CONSTANT_Double
intshortcharbyteboolean CONSTANT_Integer
String CONSTANT_String


二,Code屬性

相比ConstantValue屬性,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];
}

前兩部分與ConstantValue屬性表同樣,表示名字索引和大小,不一樣的是被索引的名字必須爲「Code」。Code屬性只能夠出如今方法表(method_info)中,可是若是一個方法爲abstract或者native的,那麼其方法表不能夠包含Code屬性表。不然必須有且只有一個屬性表。

    1) max_stack:代表方法執行的任意時刻該方法操做數棧的最大深度。

    2) max_locals:代表方法執行的任意時刻該方法的本地變量表中變量的最多個數。

    關於操做數棧,本地變量表等運行時內存的相關知識,下篇文章深刻分析。

    3) code_length:顧名思義,代表了方法體的字節碼大小。

    4) code[code_length]:這裏即是全部方法體字節碼的真正所在地了!!JVM規範對這塊有很長篇幅的約束,如長度大於0小於65535等等已超出本文範圍,不作深究,感興趣能夠查看http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.9

接下來的兩個字節exception_table_length,定義了方法中異常表的個數。異常表的結構JVM沒有單獨定義,而是直接定義在了Code屬性表中,每一個異常表表示一個異常處理塊,從上面代碼能夠看出,每一個異常表有4個U2字節:

    1) start_pc:異常處理塊的開始字節碼索引(code[code_length]中)取值範圍是[0, end_pc).

    2) end_pc:異常處理塊的結束字節碼索引(code[code_length]中)取值範圍是(start_pc, code_length).

這裏有個有趣的地方,start_pic被定義爲inclusive的,就是能夠包含第start_pc個字節碼, 而end_pc被定義爲exclusive的,是不包含第end_pc個字節碼的,這樣就有一個問題,若是代碼長度爲65535,而且end_pc也是65535那麼最後一個字節碼指令就沒法被異常處理塊捕獲處理。這是JVM設計中的一個BUG,規範中已經指出。(嚴謹程度可見一斑)

    3) handler_pc:異常處理代碼的字節碼索引(code[code_length]中)。

    5) catch_type:捕獲異常的類型,常量池中constant_class_info常量的索引,若是是0則捕獲全部異常。

異常表後面是另外一個屬性表的信息了。在Code屬性表中的屬性表(可見屬性表的靈活性了吧)能夠是LineNumberTable ,LocalVariableTable  ,LocalVariableTypeTable ,and StackMapTable  中的一個或多個,主要提供IDE調試功能用的。這裏咱們就再也不分析。


八,定義

到此爲止咱們整個Class文件結構已經分析的差很少了,相信若是你從頭認真閱讀後會有很大收穫的。可是咱們上面還有一個問題沒有弄明白就是binary class or interface name, unqualified name,descriptor有什麼區別和意義。

1)binary class or interface name,在Class文件中一個類或接口的名字一般都是全限定名(包名+類名),這就稱做binary names。如java.lang.Thread。可是因爲當年ASCII中點號(.)常被用來表示某些特用意義,所以Java中用斜槓(/)來代替了它,就變成了java/lang/Thread。這就是binary class。

2)unqualified name,Class文件中變量,方法的名字以非限定名的形式保存的,簡單講就是單純的變量名或方法名,是不能包含./[;等ASCII字符的。但有個例外<init><clinit>,前者是實例初始化方法,後者是類初始化方法。

3)descriptor,用來描述變量或方法類型的字符串。即用一個或多個簡單的字符來表達Java中的不一樣類型,其對應表以下<<表八>>

BaseType Character Type Interpretation
B byte signed byte
C char Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16
D double double-precision floating-point value
F float single-precision floating-point value
I int integer
J long long integer
L ClassName ; reference an instance of class ClassName
S short signed short
Z boolean true or false
[ reference one array dimension

對於一個int類型的變量其descriptor就是 I

對於一個Object類型的變量其descriptor就是Ljava/lang/Object.

對於一個double[][]變量其descriptor就是[[[D

不過對於方法來講稍微複雜點,descriptor是按「(參數列表)返回值」的順序描述的

如Object m(int i, double d, Thread t){}方法定義的descriptor就是(IDLjava/lang/Thread)Ljava/lang/Object。

好了到此咱們的Class文件結構理論已經徹底分析完成,不過沒有例子檢驗下咱們的分析會不會太過度了。


九,實例分析

廢話不說上實例,咱們從字節碼一個一個的來推源碼,走起。。。上字節碼:

0000000: cafe babe 0000 0034 0016 0a00 0500 1209  .......4........
0000010: 0003 0013 0700 140a 0003 0012 0700 1501  ................
0000020: 0003 6f62 6a01 0012 4c6a 6176 612f 6c61  ..obj...Ljava/la
0000030: 6e67 2f4f 626a 6563 743b 0100 0669 7661  ng/Object;...iva
0000040: 6c75 6501 0001 4901 0006 3c69 6e69 743e  lue...I...<init>
0000050: 0100 0328 2956 0100 0443 6f64 6501 000f  ...()V...Code...
0000060: 4c69 6e65 4e75 6d62 6572 5461 626c 6501  LineNumberTable.
0000070: 0004 6d61 696e 0100 1628 5b4c 6a61 7661  ..main...([Ljava
0000080: 2f6c 616e 672f 5374 7269 6e67 3b29 5601  /lang/String;)V.
0000090: 000a 536f 7572 6365 4669 6c65 0100 0d4a  ..SourceFile...J
00000a0: 766d 436c 6173 732e 6a61 7661 0c00 0a00  vmClass.java....
00000b0: 0b0c 0008 0009 0100 084a 766d 436c 6173  .........JvmClas
00000c0: 7301 0010 6a61 7661 2f6c 616e 672f 4f62  s...java/lang/Ob
00000d0: 6a65 6374 0021 0003 0005 0000 0002 0002  ject.!..........
00000e0: 0006 0007 0000 0002 0008 0009 0000 0002  ................
00000f0: 0001 000a 000b 0001 000c 0000 0027 0002  .............'..
0000100: 0001 0000 000b 2ab7 0001 2a10 17b5 0002  ......*...*.....
0000110: b100 0000 0100 0d00 0000 0a00 0200 0000  ................
0000120: 0100 0400 0300 0900 0e00 0f00 0100 0c00  ................
0000130: 0000 3400 0300 0200 0000 14bb 0003 59b7  ..4...........Y.
0000140: 0004 4c2b 59b4 0002 102d 60b5 0002 b100  ..L+Y....-`.....
0000150: 0000 0100 0d00 0000 0e00 0300 0000 0600  ................
0000160: 0800 0700 1300 0800 0100 1000 0000 0200  ................
0000170: 110a                                     ..

上面的代碼爲某個類的Class文件的十六進制形式。每行有16個字節。好了咱們按照規則解讀,前八個字節跳過,從第九個字節開始的兩個字節是常量池大小, 22,說明有21個常量(緣由見上文)。爲了節省版面咱們只分析前兩個常量池中的常量及其值:

#1 0a00 0500 12 
#2 0900 0300 13

咱們知道常量表的第一個字節代表了常量的類型,因此前兩個常量表的類型分別是10(constant_method_ref)和9(constant_field_ref)。類型判定那麼該常量表的結構也就都判定了,後面的兩個字節是對class常量的索引,索引值分別爲5和3,也就是第5和第3個常量。再後面兩個字節是對NameAndType常量的索引,索引值分別爲18和19,也就是第18和19兩個常量。而後在查找3,5,18,19個常量以此類推就能夠推出全部常量的值了。咱們很少贅述。

常量池完了是this_class, super_class, interfaces咱們跳過,直奔field_info,我已經計算出了field_info的起始位置,直接列出:

0002 0002 0006 0007 0000 0002 0008 0009 0000 //

咱們知道field_info前的兩個字節是變量個數,0002也就是2個變量,接下來就進入第一個變量表了,變量表的前兩個字節是訪問符,0002,對照<<表四>>可知是private,接下來的兩個U2字節分別是名字常量索引和描述符常量索引,索引值分別是6和7,查找常量池發現分別是obj和Ljava/lang/Object,再接下來的兩個字節是屬性個數,0000說明沒有屬性。這樣第一個變量咱們就知道它的定義以下:

private Object obj;

一樣分析第二個變量:

private int ivalue = 23;

變量表分析完了下面是方法表:

0002 0001 000a 000b 0001 000c 0000 0027 0002 
0001 0000 000b 2ab7 0001 2a10 17b5 0002 b100 00

前兩個字節仍是方法個數,0002,說明兩個方法,接下來進入方法表,方法表前兩個字節是方法的訪問修飾符,0001查看<<表五>>可知是public的。接下來的兩個U2字節分別是名字常量索引和描述符常量索引,索引值分別是10(000a)和11(000b),查找常量池發現分別是<init>和()V。接着後面兩個字節是屬性表個數,0001,說明有一個屬性,進入屬性表,前兩個字節是屬性名的索引,000c查看常量池是「Code」,說明是Code屬性,Code屬性表的3,4,5,6四個字節是屬性表的字節大小,0000 0027=39。說明有39個字節。而後是0002(2)最大棧深,0001(1)最大本地表量表,以及0000 000b=11,代表有11個字節碼是真正的方法體。

2ab7 0001 2a10 17b5 0002 b1

咱們先不理會方法體的內容,最後的0000代表該方法沒有異常處理塊。

到此咱們可大體寫出該方法的定義了

void <init>(){
    //....
}

這個方法就是實例初始化方法,也就是默認無參構造器。( 以上分析只是爲了瞭解Class文件結構,JDK提供了一個分析字節碼的工具javap,能夠快速簡單的分析字節碼)

好了到此爲止全部分析就算完成了。但願你會有所收穫。


文獻

http://docs.oracle.com/javase/specs/jvms/se7/html/  (The Java Virtual Mathine Specification, Java SE 7 Edition.)

相關文章
相關標籤/搜索