咱們知道Java最有名的宣傳口號就是:「一次編寫,處處運行(Write Once,Run Anywhere)」,而其平臺無關性則是依賴於JVM, 全部的java文件都被編譯成字節碼(class)文件,而虛擬機只須要認識字節碼文件就能夠了。想要弄懂虛擬機以及類加載機制,這部份內容是不可不知的。
html
Class文件是一組以8字節爲基礎單位的二進制流,全部數據無間隔的排列在Class文件之中,多字節數據以大端(big-endian order)的方式存儲。Class文件以一種接近於C中結構體的僞代碼形式存儲數據結構,而且只包含無符號數和表兩種數據結構:java
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文件由10個部分組成:
1 . 魔數
2 . Class文件主次版本號
3 . 常量池
4 . 訪問標記
5 . 當前類名
6 . 父類名
7 . 繼承的接口名
8 . 包含的全部字段的數量+字段
9 . 包含的全部方法的數量+方法
10 . 包含的全部屬性的數量+屬性bash
下面咱們依次對每一個部分進行分析:數據結構
魔數(Magic number)用來肯定文件類型,這裏就是檢測文件是不是可以被虛擬機接受的Class文件。不少文件都使用魔數來肯定文件類型,而不是擴展名(由於擴展名能夠任意修改)。能夠參看個人深刻理解程序構造(一)。併發
Class文件的魔數是「0xcafebabe」,咖啡寶貝?Java自己也是一種爪哇咖啡,真是挺有緣的。
這裏我也寫個小的測試程序,來看看它的二進制碼流:oracle
package com.shuqing28;
public class TestClass {
private int m;
public int inc() {
return m+1;
}
}複製代碼
咱們使用javac編譯成.class文件,Windows下可使用WinHex打開,Linux下則可使用hexdump打開二進制,命令以下:app
$ hexdump -C TestClass.class
00000000 ca fe ba be 00 00 00 34 00 16 0a 00 04 00 12 09 |.......4........|
00000010 00 03 00 13 07 00 14 07 00 15 01 00 01 6d 01 00 |.............m..|
00000020 01 49 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 |.I...<init>...()|
00000030 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e |V...Code...LineN|
00000040 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63 |umberTable...Loc|
00000050 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 |alVariableTable.|
00000060 00 04 74 68 69 73 01 00 19 4c 63 6f 6d 2f 73 68 |..this...Lcom/sh|
00000070 75 71 69 6e 67 32 38 2f 54 65 73 74 43 6c 61 73 |uqing28/TestClas|
00000080 73 3b 01 00 03 69 6e 63 01 00 03 28 29 49 01 00 |s;...inc...()I..|
00000090 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0e 54 65 |.SourceFile...Te|
000000a0 73 74 43 6c 61 73 73 2e 6a 61 76 61 0c 00 07 00 |stClass.java....|
000000b0 08 0c 00 05 00 06 01 00 17 63 6f 6d 2f 73 68 75 |.........com/shu|
000000c0 71 69 6e 67 32 38 2f 54 65 73 74 43 6c 61 73 73 |qing28/TestClass|
000000d0 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a |...java/lang/Obj|
000000e0 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 |ect.!...........|
000000f0 05 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 |................|
00000100 09 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 |..../........*..|
00000110 01 b1 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 |................|
00000120 00 03 00 0b 00 00 00 0c 00 01 00 00 00 05 00 0c |................|
00000130 00 0d 00 00 00 01 00 0e 00 0f 00 01 00 09 00 00 |................|
00000140 00 31 00 02 00 01 00 00 00 07 2a b4 00 02 04 60 |.1........*....`|
00000150 ac 00 00 00 02 00 0a 00 00 00 06 00 01 00 00 00 |................|
00000160 06 00 0b 00 00 00 0c 00 01 00 00 00 07 00 0c 00 |................|
00000170 0d 00 00 00 01 00 10 00 00 00 02 00 11 |.............|
0000017d複製代碼
看第一行的前4個字節的十六進制就是0xcafebabe
,因此文件類型確實爲.class文件。jvm
第5和第6字節是次版本號(Minor Version),第7和第8字節是主版本號(Major Version)。這裏看出咱們的主版本號是0x0034
,也就是52,下面是JDK與其對應的版本號關係:ide
JDK 1.8 = 52
JDK 1.7 = 51
JDK 1.6 =50
JDK 1.5 = 49
JDK 1.4 = 48
JDK 1.3 = 47
JDK 1.2 = 46
JDK 1.1 = 45
能夠看出我使用的是Java8編譯的代碼。
咱們繼續看二進制文件的第一行:
00000000 ca fe ba be 00 00 00 34 00 16 0a 00 04 00 12 09 |.......4........|複製代碼
在主版本號0x0034
後的是0x0016
,這個值表示常量池的容量。常量池能夠理解爲Class文件的資源倉庫,常量池中包含的數據結構是這樣的:
cp_info {
u1 tag;
u1 info[];
}複製代碼
常量池中的每一個項目都包含一個tag開頭的cp_info對象,表明着常量類型,info則根據不一樣的類型各有各的結構。目前一共有14種常量類型:
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 |
上面的0x0016
翻譯成十進制是22,那麼常量池中有21個常量,由於常量池中索引是從1開始計數的,因此常量索引範圍是1~21。
00000000 ca fe ba be 00 00 00 34 00 16 0a 00 04 00 12 09 |.......4........|複製代碼
接下看常量池的第一個常量, tag是0x0a
, 查上面的常量表就是CONSTANT_Methodref,表示接下來定義的是一個方法,知道類型後,咱們能夠查一下CONSTANT_Methodref的結構,這裏能夠參考Oracle的官方文檔The class File Format,
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}複製代碼
因爲.class文件是無間隔的二進制文件,因此接着讀:
0x0a
,上面已經說了指代CONSTANT_Methodref常量由於class_index佔兩個字節,因此緊接着讀到了0x0004
,也就是4,指向常量池中的第4個常量,name_and_type_index是0x0012
,指向第18個常量。後面會分析到第4和第18個常量。
繼續往下讀,到第一行的最末了,是個0x09,指示的是CONSTANT_Fieldref,表示接下來是對一個域的定義, 查官方文檔,格式爲:
CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}複製代碼
結構和CONSTANT_Methodref_info同樣,這時候讀到了第二行:
00000010 00 03 00 13 07 00 14 07 00 15 01 00 01 6d 01 00 |.............m..|複製代碼
class_index爲0x0003
,指向第3個常量,name_and_type_index爲0x0013
指向第13個常量。這時候繼續日後讀,終於讀到第3個常量了。此時tag是0x07
,查表可得爲CONSTANT_Class類型,此類型的常量表明一個類或者接口的符號引用,CONSTANT_Class的結構:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}複製代碼
tag是7, name_index是0x0014
,十進制就是20,指向第20個常量,這樣咱們已經讀了不少個字節了。可是這樣解析下去很累,還好java自帶的javap工具能夠幫咱們分析出字節碼的內容。
執行下面語句:
javap -verbose TestClass.class複製代碼
咱們能夠獲得:
Last modified Nov 14, 2017; size 381 bytes
MD5 checksum 102d643185c4823ef103931ff3e34462
Compiled from "TestClass.java"
public class com.shuqing28.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#19 // com/shuqing28/TestClass.m:I
#3 = Class #20 // com/shuqing28/TestClass
#4 = Class #21 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/shuqing28/TestClass;
#14 = Utf8 inc
#15 = Utf8 ()I
#16 = Utf8 SourceFile
#17 = Utf8 TestClass.java
#18 = NameAndType #7:#8 // "<init>":()V
#19 = NameAndType #5:#6 // m:I
#20 = Utf8 com/shuqing28/TestClass
#21 = Utf8 java/lang/Object
{
public com.shuqing28.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
...//省略複製代碼
這裏咱們能夠看到Constant pool字段,後面依次列出了21個常量,能夠看出第一個是Methodref型的常量,class_index指向第4個常量,第4個常量呢是CONSTANT_Class類型,name_index又指向第20個常量,可知是一個CONSTANT_Utf8類型的常量,前面沒說到CONSTANT_Utf8,下面是它的結構:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}複製代碼
第一位tag爲1,length指示字符數組的長度,bytes[length]是使用UTF-8縮略編碼表示的字符串,這裏解析出來是com/shuqing28/TestClass,即類的全限定名。
繼續回到第一個Methodref常量,它的name_and_type_index值是18, 繼續找到第18個常量,是CONSTANT_NameAndType_info類型,表明的是一個方法的信息:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}複製代碼
name_index指向了常量7, 即#7 = Utf8 <init>
, 是一個CONSTANT_Utf8_info類型,值爲,這個是方法的名稱,descriptor_index指向了常量8,即#8 = Utf8 ()V
,是方法的描述,下文會說這個表達式是什麼意思。
這樣咱們就能夠一一把這21個常量分析清楚了。
其實Class文件就是在一開始列出了一堆常量,後面的各類描述都是各類index,指向前面常量池中的各類常量,來描述整個類的定義。就像有一本字典,咱們使用字典中的字來造咱們的句子,只不過Class文件中造句是有嚴格格式規定的,下面的內容基本都按照固定格式,無間隔的描述一個類的內容。
常量池結束後,緊接着的兩個字節表明訪問標誌(access_flags),這個標誌用於識別一些類或者接口的訪問信息,包括這個Class是類仍是接口,是不是public的,是不是abstract,是不是final的。
訪問標記含義以下表:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
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. |
access_flags中一共有16個標誌位可用,當前只定義了8個,別的都爲0,TestClass是public類型的,且使用JDK1.2之後的編譯器進行編譯的(使用JDK1.2之後的編譯器編譯,這個值都爲真),別的標誌都爲假。因此access_flags的值應爲:0x0001|0x0020 = 0x0021
。咱們找到剛纔常量池最後一行的地方:
000000e0 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 |ect.!...........|複製代碼
65 63 74
分別對應ect,緊接着是0x0021
,與咱們的分析結果一致。
引用文章開頭的ClassFile的數據結構,這三項定義爲:
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];複製代碼
類索引和父類索引都是u2類型的數據,而接口索引首先給出了接口的數量,而後纔是一個包含接口的數組。這三個值揭示了一個類的繼承關係。
000000e0 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 |ect.!...........|複製代碼
接着前面的0x0021
看,類索引爲0x0003
,指示常量池第3個常量,查上文可得#3 = Class #20 // com/shuqing28/TestClass
,第3個常量又指向第20個常量,而第20個常量是一個CONSTANT_Utf8變量,其值爲com/shuqing28/TestClass,表示類的全限定名字符串。
接下來的是0x0004
是父類索引,指向常量池中第4個常量,即#4 = Class #21 // java/lang/Object
, 又指向第21個變量,即java/lang/Object,咱們知道Object是全部類的父類。
接下來的是0x0000
,可見TestClass沒有實現任何接口。
字段表用於描述接口或者類中聲明的變量。字段包括類級別的變量以及實例級的變量,可是不包括方法內的局部變量。一個Java字段能夠包括如下信息:字段的做用域、是實例變量仍是類變量、是不是final、併發可見性(volatile),是否能夠被序列化(transient)、字段數據類型。下面是字段表具體結構:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}複製代碼
再看access_flags能夠取如下值:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
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. |
通常來講,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三個標誌最多隻能存在一個,其它標誌都按照Java語言自己的性質來。
在access_flags標誌的後面是兩項索引值name_index,descriptor_index,兩個都是指向常量池的索引,分別表明字段的簡單名稱以及字段和方法的描述符。
這裏咱們梳理下簡單名稱、描述符以及全限定名這三個詞對應的概念:
全限定名:前面提到的com/shuqing28/TestClass就是全限定名,它把java代碼中全部的"."替換成了"/",通常使用";"結尾。
簡單名稱:不帶類型和修飾的方法或者字段名,上文中的代碼裏就是"inc"和"m"
至於方法描述符,描述的是數據類型、方法的參數列表和返回值。咱們知道在C++中重載函數時函數其實是換了名字的,包含了函數的參數,例如add(int x, int y),在編譯後多是Add_Int_Int, 可是在Java中咱們把基本數據類型都用一個大寫字符來表示,而對象類則是使用L+對象的全限定名來表示。
描述符標識字符含義:
標識字符 | 含義 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | Object, 例如 Ljava/lang/Object |
對於數組,前面加[
就行,如java.lang.String[][]
,表達爲[[java/lang/String
, int[]
就被記錄爲[I
。
用描述符描述方法時,按照參數列表,返回值的順序描述,參數列表還須要放在括號內。好比前文說起的"() V" 就表示一個參數爲空,返回值爲void
的方法,即代碼中的void inc()
方法。
舉個複雜點的, int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)
,其描述符爲([CII[CIII) I
。
繼續分析咱們前文中說起的程序的二進制代碼:
000000e0 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 |ect.!...........|
000000f0 05 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 |................|複製代碼
上一小節咱們分析到第一行的0x0000
了,接下來的是0x01
,這個值其實表明了字段表的個數,咱們的代碼裏只包含一個字段。接下來的是0x0002
,這個字段是access_flags標誌,查詢後可知爲ACC_PRIVATE,再接下來是0x0005
, 從常量表清單上能夠查到是#5 = Utf8 m
, 再接着是descriptor_index, 其值爲0x0006
,查一下常量池爲#6 = Utf8 I
,可知這一句爲private int m;
通常來講,在decriptor_index後,還有個屬性集合用於存儲一些額外信息,而0x0000
表明沒有屬性字段。
若是把m字段聲明爲private static int m = 123;
則可能多一個ConstantValue屬性,指向常量值123。
方法表集合和字段表集合很是類似,結構也是:
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}複製代碼
只不過在訪問標誌和屬性表集合的可選項有所不一樣。例如access_flags有如下可選值:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public ; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declaredprivate ; accessible only within the defining class. |
ACC_PROTECTED | 0x0004 | Declaredprotected ; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declaredstatic . |
ACC_FINAL | 0x0010 | Declaredfinal ; must not be overridden |
ACC_SYNCHRONIZED | 0x0020 | Declaredsynchronized ; 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 | Declarednative ; implemented in a language other than Java. |
ACC_ABSTRACT | 0x0400 | Declaredabstract ; no implementation is provided. |
ACC_STRICT | 0x0800 | Declaredstrictfp ; floating-point mode is FP-strict. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
能夠看出,方法裏增長了像ACC_SYNCHRONIZED
,ACC_NATIVE
,ACC_STRICT
, ACC_ABSTRACT
, 分別對應着synchronized
、native
、strictfp
、abstract
這些只能修飾方法的關鍵字。
如今咱們就能夠繼續分析咱們程序的二進制代碼了。
000000f0 05 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 |................|
00000100 09 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 |..../........*..|複製代碼
上一小節咱們剛剛分析到000000f0行的0x0000,接下來的是0x0002
,表明有兩個方法,接下來的幾個字節是
0x0001
:訪問標記是ACC_PUBLIC0x0007
:名稱索引指向第7個常量:0x0008
:描述符索引指向第8個常量:()V0x0001
:屬性有一個0x0009
:屬性指向第9個常量,Code咱們正好有疑問,方法定義有了,方法體在哪呢,答案就是上面分析的最後一個Code。下一節就說說屬性表集合的各類可能。
屬性表(attribute_info)在前面已經屢次說起,Class文件、字段表、方法表中均可以攜帶本身的屬性表集合,用於描述某些場景轉有的信息。
屬性表並無嚴格限制順序,只要不與已有屬性名重複,任何人實現的編譯器均可以添加本身定義的屬性信息,如下是一些預約義的屬性:
屬性名稱 | 使用位置 | 含義 |
---|---|---|
SourceFile | ClassFile | 記錄源文件的名稱 |
InnerClasses | ClassFile | 內部類列表 |
EnclosingMethod | ClassFile | 內部類纔有這個屬性,用於標識這個類所在的外圍方法 |
SourceDebugExtension | ClassFile | 用於存儲額外的調試信息,JDK1.6中新增 |
BootstrapMethods | ClassFile | 用於保存invokeddynamic指令引用的引導方法限定符,JDK1.7中新增 |
ConstantValue | field_info | final關鍵字定義的常量值 |
Code | method_info | Java代碼編譯成的字節碼指令 |
Exceptions | method_info | 方法拋出的異常 |
RuntimeVisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations | method_info | 指明哪些參數是運行時可見的,哪些是運行時不可見的,JDK1.5中新增 |
AnnotationDefault | method_info | 記錄註解類元素的默認值,JDK1.5中新增的 |
MethodParameters | method_info | 記錄方法的參數信息,好比它們的名字,訪問級別,JDK1.8新增 |
Synthetic | ClassFile, field_info, method_info | 表示方法或字段是編譯器自動生成的 |
Deprecated | ClassFile, field_info, method_info | 被聲明爲deprecated的字段 |
Signature | ClassFile, field_info, method_info | 用於支持泛型狀況下的方法簽名,在Java語言中,若是任何類、接口、初始化方法或者成員的泛型簽名包含了類型變量或者參數化類型,則Signature屬性會爲它記錄泛型簽名信息。因爲Java的泛型採用擦除法實現,在爲了不類型信息被擦除後致使簽名混亂,須要這個屬性記錄泛型中的相關信息。JDK1.5中新增 |
RuntimeVisibleAnnotations, RuntimeInvisibleAnnotations | ClassFile, field_info, method_info | 爲動態註解提供支持,指明哪些是註解是運行時可見的,哪些是運行時不可見的,JDK1.5中新增 |
LineNumberTable | Code | Java源碼的行號與字節碼指令的對應關係 |
LocalVariableTable | Code | 方法的局部變量描述 |
LocalVariableTypeTable | Code | 使用特徵簽名代替描述符,是爲了引入泛型語法以後能描述泛型參數化類型而添加,JDK1.5中新增 |
StackMapTable | Code | 供新的類型檢查驗證器(Type Checker)檢查和處理目標方法的局部變量和操做棧所須要的類型是否匹配,JDK1.6新增 |
RuntimeVisibleTypeAnnotations, RuntimeInvisibleTypeAnnotations | ClassFile, field_info, method_info, 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];
}複製代碼
this
)、異常處理器的參數、方法體定義的局部變量都須要局部變量表來存放。可是max_locals並非全部局部變量所佔的slot之和,由於slot能夠重用,當一個變量超出做用域了,該slot又會給別的局部變量使用,編譯器會根據做用域計算max_locals。Exceptions屬性在方法表中與Code屬性平級,注意和上面Code中的異常表不一樣,Exceptions屬性的做用是列出方法可能拋出的異常,Exceptions屬性表的結構:
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}複製代碼
LineNumber用來記錄Java源碼與字節碼行號之間的對應關係,咱們在編譯代碼時也可使用-g: none
或-g: line
來取消生成這個屬性,不過在調試代碼時就看不到行號了,也沒法打斷點。
LineNumberTable的數據結構以下:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length];
}複製代碼
咱們主要看line_number_table,start_pc是字節碼行號,line_number是Java源碼行號。
LocalVariableTable屬性用於描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關係,咱們在編譯代碼時也可使用-g: none
或-g: vars
來取消生成這個屬性,可是若是取消的話,IDE會用arg0,arg1這樣的參數來取代原有的參數名,致使調試時不清晰。
LocalVariableTable的數據結構以下:
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
} local_variable_table[local_variable_table_length];
}複製代碼
主要介紹local_variable_table:
ConstantValue是一個定長屬性,用來通知虛擬機爲靜態變量賦值,若是同時定義了int x=3;
和static int y=3;
則虛擬機爲x,y賦值的時機不一樣,對於x,是在實例構造器<init>
中進行的,而static類型的變量,則會在類構造器<clinit>
方法中或者使用ConstantValue屬性。
目前javac編譯器的規則是,若是同時有final和static修飾,則是使用ConstantValue屬性,只有static時,而且變量類型是基本類型或者String時,就會在<clinit>
中進行初始化。
若是類中定義了內部類,則會使用InnerClasses屬性來記錄內部類和宿主的關係。
InnerClasses的數據結構以下:
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes; //記錄有多少個內部類
{ u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
} classes[number_of_classes];
}複製代碼
仍是隻看classes字段,inner_class_info_index指向內部類的符號引用,outer_class_info_index指向宿主類的符號引用,inner_name_index指向內部類的名稱,若是是匿名內部類,則爲0,inner_class_access_flags是內部類的訪問標誌,見下表:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | Marked or implicitly public in source. |
ACC_PRIVATE | 0x0002 | Marked private in source. |
ACC_PROTECTED | 0x0004 | Marked protected in source. |
ACC_STATIC | 0x0008 | Marked or implicitly static in source. |
ACC_FINAL | 0x0010 | Marked final in source. |
ACC_INTERFACE | 0x0200 | Was an interface in source. |
ACC_ABSTRACT | 0x0400 | Marked or implicitly abstract in source. |
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. |
還有其它的一些屬性,若是想了解,能夠看一下參考資料。
參考資料: