JVM系列文章(三):Class檔案內容解析
作為一個程式猿,只知道怎麼用是遠遠不夠的。起碼,你須要知道為什麼能夠這麼用。即我們所謂底層的東西。
那究竟什麼是底層呢?我認為這不能一概而論。以我如今的知識水平而言:對於Web開發人員,TCP/IP、HTTP等等協議可能就是底層;對於C、C++程式猿。記憶體、指標等等可能就是底層的東西。那對於Java開發人員。你的Java程式碼執行所在的JVM可能就是你所須要去了解、理解的東西。
我會在接下來的一段時間,和讀者您一起去學習JVM。全部內容均參考自《深入理解Java虛擬機器:JVM高階特性與最佳實踐》(第二版),感謝作者。
本文是系列文章第三篇。講述的是類檔案結構。文中的部分內容來自http://blog.csdn.net/a19881029/article/details/16117251(也是此書閱讀筆記),感謝先行者。
一、概述
不論什麼一個Class檔案都相應唯一一個類或介面的定義資訊,可是不是全部的類或介面都得定義在檔案裡(它們也能夠通過類載入器直接生成)。
Class檔案是一組以8位位元組為基礎單位的二進位制流。各個資料項嚴格按順序排列,沒有不論什麼分隔符。
Class檔案格式採用一種類似於C語言結構體的偽結構來儲存資料。這樣的偽結構僅僅有兩種資料型別:無符號數和表。
無符號數:是基本資料型別。以u1、u2、u4、u8分別代表1個位元組、2個位元組、4個位元組、8個位元組的無符號數,能夠用來描寫敘述數字、索引引用、數量值或者依照UTF-8編碼構成的字串值。
表:由多個無符號數或者其它表作為資料項構成的複合資料型別。全部表都習慣性地以“_info”結尾。整個Class檔案本質上就是一張表,例如以下所看到的:
型別 |
名稱 |
數量 |
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 |
接下來分別對錶中的各個欄位作出解釋。
二、各個欄位具體解釋
使用以下的類進行說明:
package com.test; public class Test { private int m; public int getM(){ return m + 1; } }
編譯後的class檔案例如以下:
1.魔數
每一個class檔案的頭4個位元組稱為魔數,它唯一的作用是確定這個檔案是否為一個能被虛擬機器接受的Class檔案。
非常多檔案儲存標準中都使用魔數來進行身份識別。譬如圖片格式gif、jpeg等。使用魔數而不是拓展名來進行識別主要是基於安全方面的考慮,由於檔案拓展格式能夠任意修改。
Class檔案的魔數為:0xCAFEBABE(咖啡寶貝?)。這個魔數似乎也預示著日後JAVA這個商標名稱的出現。
2.版本
第五六個位元組是次版本(Minor Version)。第7和第8個位元組是主版本(Major Version)。
高版本號的JDK能夠向下相容曾經版本號的Class檔案,可是無法執行以後版本號的Class檔案,即使檔案格式並未發生變化,虛擬機器也必須拒絕執行超過其版本號號的Class檔案。
3.常量池
常量池能夠理解為Class檔案之中的資源倉庫,是Class檔案結構中與其它專案關聯最多的資料型別,也是佔用Class檔案空間最大的資料專案之中的一個。同一時候也是在Class檔案裡第一個出現的表型別資料專案。
因為常量池中常量的數目是不固定的,所以在常量池入口須要放置一個2位元組長的無符號數constatn_pool_count來代表常量池容量計數值。這個容量計數從1而不是0開始。
constant_pool_count:佔2位元組。0x0016。轉化為十進位制為22,即說明常量池中有21個常量(僅僅有常量池的計數是從1開始的,其他集合型別均從0開始),索引值為1~22。第0項常量具有特殊意義。假設某些指向常量池索引值的資料在特定情況下須要表達“不引用不論什麼一個常量池專案”的含義,這樣的情況能夠將索引值置為0來表示
常量池中主要存放兩大類常量:字面量和符號引用。字面量如文字字串、宣告為final的常量值等。符號引用包含三類常量:類和介面的全限定名、欄位的名稱和描寫敘述符、方法的名稱和描寫敘述符。
常量池中的每一項常量都是一個表,在JDK1.7之前共同擁有11種結構各不同樣的表資料結構。這些表資料結構在表開始的第一位是一個u1型別的標誌位,代表當前這個常量屬於那種常量型別。例如以下表所看到的:
型別 |
簡單介紹 |
專案 |
型別 |
描寫敘述 |
CONSTANT_Utf8_info |
utf-8縮略編碼字串 |
tag |
u1 |
值為1 |
length |
u2 |
utf-8縮略編碼字串佔用位元組數 |
||
bytes |
u1 |
長度為length的utf-8縮略編碼字串 |
||
CONSTANT_Integer_info |
整形字面量 |
tag |
u1 |
值為3 |
bytes |
u4 |
依照高位在前儲存的int值 |
||
CONSTANT_Float_info |
浮點型字面量 |
tag |
u1 |
值為4 |
bytes |
u4 |
依照高位在前儲存的float值 |
||
CONSTANT_Long_info |
長整型字面量 |
tag |
u1 |
值為5 |
bytes |
u8 |
依照高位在前儲存的long值 |
||
CONSTANT_Double_info |
雙精度浮點型字面量 |
tag |
u1 |
值為6 |
bytes |
u8 |
依照高位在前儲存的double值 |
||
CONSTANT_Class_info |
類或介面的符號引用 |
tag |
u1 |
值為7 |
index |
u2 |
指向全限定名常量項的索引 |
||
CONSTANT_String_info |
字串型別字面量 |
tag |
u1 |
值為8 |
index |
u2 |
指向字串字面量的索引 |
||
CONSTANT_Fieldref_info |
欄位的符號引用 |
tag |
u1 |
值為9 |
index |
u2 |
指向宣告欄位的類或介面描寫敘述符CONSTANT_Class_info的索引項 |
||
index |
u2 |
指向欄位描寫敘述符CONSTANT_NameAndType_info的索引項 |
||
CONSTANT_Methodref_info |
類中方法的符號引用 |
tag |
u1 |
值為10 |
index |
u2 |
指向宣告方法的類描寫敘述符CONSTANT_Class_info的索引項 |
||
index |
u2 |
指向名稱及型別描寫敘述符CONSTANT_NameAndType_info的索引項 |
||
CONSTANT_InterfaceMethodref_info |
介面中方法的符號引用 |
tag |
u1 |
值為11 |
index |
u2 |
指向宣告方法的介面描寫敘述符CONSTANT_Class_info的索引項 |
||
index |
u2 |
指向名稱及型別描寫敘述符CONSTANT_NameAndType_info的索引項 |
||
CONSTANT_NameAndType_info |
欄位或方法的部分符號引用 |
tag |
u1 |
值為12 |
index |
u2 |
指向該欄位或方法名稱常量項的索引 |
||
index |
u2 |
指向該欄位或方法描寫敘述符常量項的索引 |
首先來看常量池中的第一項常量,其標誌位為0x07,是一個CONSTANT_Class_info型別常量。此型別常量代表一個類或介面的符號引用。依據其資料結構,接下來2位位元組用來儲存一個索引值,它指向常量池中一個CONSTANT_Utf8_info型別的常量,此常量代表了這個類或介面的全限定名,索引值為0x0002。即指向了常量池中的第二項常量。
第二項常量標誌位為0x01。確實是一個CONSTANT_Utf8_info型別的常量。依據其資料結構。接下來2個位元組用來儲存utf-8縮略編碼字串長度,其值為0x000D,轉化為十進位制為13,即接下來的13個位元組為一個utf-8縮略編碼的字串。為com/test/Test。能夠看到正好是測試類的全限定名。
4.訪問標誌
在常量池結束之後,緊接著的兩個位元組代表訪問標誌。用於識別一些類或者介面層次的訪問資訊。例如以下表所看到的。
志名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
是否為public型別 |
ACC_FINAL |
0x0010 |
是否被宣告為final,僅僅有類可設定 |
ACC_SUPER |
0x0020 |
是否同意使用invokespecial位元組碼指令,JDK1.2以後編譯出來的類這個標誌為真 |
ACC_INTERFACE |
0x0200 |
標識這是一個介面 |
ACC_ABSTRACT |
0x0400 |
是否為abstract型別,對於介面和抽象類,此標誌為真。其他類為假 |
ACC_SYNTHETIC |
0x1000 |
標識別這個類並不是由使用者程式碼產生 |
ACC_ANNOTATION |
0x2000 |
標識這是一個註解 |
ACC_ENUM |
0x4000 |
標識這是一個列舉 |
依據上面的表格,測試類的訪問標誌0x0021= 0x0001 | 0x0020 =ACC_PUBLIC | ACC_SUPER
5.類索引、父類索引和介面索引集合
Class檔案裡由這3項資料來確定這個類的繼承關係
this_class:類索引,用於確定這個類的全限定名,佔2位元組
super_class:父類索引。用於確定這個類父類的全限定名(Java語言不同意多重繼承,故父類索引僅僅有一個。
除了java.lang.Object類之外全部類都有父類,故除了java.lang.Object類之外,全部類該欄位值都不為0),佔2位元組
interfaces_count:介面索引計數器。佔2位元組。
假設該類沒有實現不論什麼介面。則該計數器值為0,而且後面的介面的索引集合將不佔用不論什麼位元組。
interfaces:介面索引集合,一組u2型別資料的集合。用來描寫敘述這個類實現了哪些介面。這些被實現的介面將按implements語句(假設該類本身為介面,則為extends語句)後的介面順序從左至右排列在介面的索引集合中
this_class、super_class與interfaces中儲存的索引值均指向常量池中一個CONSTANT_Class_info型別的常量。通過這個常量中儲存的索引值能夠找到定義在CONSTANT_Utf8_info型別的常量中的全限定名字串
this_class的值為0x0001,即常量池中第一個常量,super_class的值為0x0003,即常量池中的第三個常量,interfaces_counts的值為0x0000,故介面索引集合大小為0
6.欄位表集合
欄位表用於描寫敘述介面或者類中宣告的變數,包含類級變數和例項級變數(是否是static)。但不包含在方法內部宣告的區域性變數。
fields_count:欄位表計數器。即欄位表集合中的欄位表資料個數。佔2位元組,其值為0x0001,即僅僅有一個欄位表資料。也就是測試類中僅僅包括一個變數(不算方法內部變數)
fields:欄位表集合,一組欄位表型別資料的集合。欄位表用於描寫敘述介面或類中宣告的變數。包含類級別(static)和例項級別變數,不包含在方法內部宣告的變數
在Java中一般通過例如以下幾項描寫敘述一個欄位:欄位作用域(public、protected、private修飾符)、是類級別變數還是例項級別變數(static修飾符)、可變性(final修飾符)、併發可見性(volatile修飾符)、可序列化與否(transient修飾符)、欄位資料型別(基本型別、物件、陣列)以及欄位名稱。
在欄位表中,變數修飾符使用標誌位表示,欄位資料型別和欄位名稱則引用常量池中常量表示,欄位表格式例如以下表所看到的:
型別 |
名稱 |
數量 |
u2 |
access_flags |
1 |
u2 |
name_index |
1 |
u2 |
descriptor_index |
1 |
u2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
欄位修飾符放在access_flags中,佔2位元組,其值為0x0002,可見這個欄位由private修飾,與訪問標誌位十分相似
標誌名稱 |
標誌值 |
含義 |
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_SYNTHETIC |
0x1000 |
欄位是否為編譯器自己主動產生 |
ACC_ENUM |
0x4000 |
欄位是否為enum |
7.方法表集合
methods_count:方法表計數器,即方法表集合中的方法表資料個數。
佔2位元組,其值為0x0002,即測試類中有2個方法(還自己主動添加了一個建構函式)
methods:方法表集合,一組方法表型別資料的集合。
方法表結構和欄位表結構一樣:
型別 |
名稱 |
數量 |
u2 |
access_flags |
1 |
u2 |
name_index |
1 |
u2 |
descriptor_index |
1 |
u2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
資料項的含義很相似。僅在訪問標誌位和屬性表集合中的可選項上有稍微不同
因為ACC_VOLATILE標誌和ACC_TRANSIENT標誌不能修飾方法,所以access_flags中不包括這兩項,同一時候新增ACC_SYNCHRONIZED標誌、ACC_NATIVE標誌、ACC_STRICTFP標誌和ACC_ABSTRACT標誌
標誌名稱 |
標誌值 |
含義 |
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_BRIDGE |
0x0040 |
方法是否是由編譯器產生的橋接方法 |
ACC_VARARGS |
0x0080 |
方法是否接受不定引數 |
ACC_NATIVE |
0x0100 |
欄位是否為native |
ACC_ABSTRACT |
0x0400 |
欄位是否為abstract |
ACC_STRICTFP |
0x0800 |
欄位是否為strictfp |
ACC_SYNTHETIC |
0x1000 |
欄位是否為編譯器自己主動產生 |
第一個方法(由編譯器自己主動加入的預設構造方法):
access_flags為0x0001,即public;name_index為0x0007。即常量池中第7個常量;descriptor_index為0x0008,即常量池中第8個常量
- const #7 = Asciz <init>;
- const #8 = Asciz ()V;
接下來2個位元組為屬性計數器,其值為0x0001,說明這種方法的屬性表集合中有一個屬性。屬性名稱為接下來2位0x0009,指向常量池中第9個常量:Code。接下來4位為0x0000002F,表示Code屬性值的位元組長度為47。接下來2位為0x0001。表示該方法的運算元棧的深度最大值為1。接下來2位依舊為0x0001,表示該方法的區域性變數佔用空間為1。接下來4位為0x0000005。則緊接著的5個位元組0x2AB7000AB1為該方法編譯後生成的位元組碼指令(各位元組相應的指令不介紹了,可查詢虛擬機器位元組碼指令表)。接下來2個位元組為0x0000,說明Code屬性異常表集合為空。
接下來2個位元組為0x0002,說明Code屬性帶有2個屬性,那麼接下來2位0x000C即為Code屬性第一個屬性的屬性名稱,指向常量池中第12個常量:LineNumberTable。接下來4位為0x00000006。表示LineNumberTable屬性值所佔位元組長度為6。接下來2位為0x0001,即該line_number_table中僅僅有一個line_number_info表,start_pc為0x0000,line_number為0x0003,LineNumberTable屬性結束。
接下來2位0x000D為Code屬性第二個屬性的屬性名。指向常量池中第13個常量:LocalVariableTable。
該屬性值所佔的位元組長度為0x0000000C=12。接下來2位為0x0001,說明local_variable_table中僅僅有一個local_variable_info表。依照local_variable_info表結構,start_pc為0x0000。length為0x0005,name_index為0x000E。指向常量池中第14個常量:this。descriptor_index為0x000F,指向常量池中第15個常量:Lcom/test/Test;。index為0x0000。
第一個方法結束
第二個方法:
access_flags為0x0001,即public。name_index為0x0010。即常量池中第16個常量。descriptor_index為0x0011,即常量池中第17個常量
- const #16 = Asciz getM;
- const #17 = Asciz ()I;
接下來2個位元組為屬性計數器,其值為0x0001,說明這種方法有一個方法屬性,屬性名稱為接下來2位0x0009,指向常量池中第9個常量:Code。接下來4位為0x00000031。表示Code屬性值的位元組長度為49。接下來2位為0x0002,表示該方法的運算元棧的深度最大值為2。接下來2位為0x0001,表示該方法的區域性變數佔用空間為1。接下來4位為0x0000007,則緊接著的7個位元組0x2AB400120460AC為該方法編譯後生成的位元組碼指令。
接下來2個位元組為0x0000。說明Code屬性異常表集合為空。
接下來2個位元組為0x0002,說明Code屬性帶有2個屬性。那麼接下來2位0x000C即為Code屬性第一個屬性的屬性名稱,指向常量池中第12個常量:LineNumberTable。
接下來4位為0x00000006。表示LineNumberTable屬性值所佔位元組長度為6。
接下來2位為0x0001。即該line_number_table中僅僅有一個line_number_info表,start_pc為0x0000。line_number為0x0007,LineNumberTable屬性結束。
和第一個方法的LocalVariableTable屬性基本同樣,唯一的差別是區域性變數this的作用範圍覆蓋的長度為7而不是5,第二個方法結束
假設子類沒有重寫父類的方法,方法表集合中就不會出現父類方法的資訊。有可能會出現由編譯器自己主動加入的方法(如:<init>。例項類構造器)
在Java語言中,過載一個方法除了要求和原方法擁有同樣的簡單名稱外。還要求必須擁有一個與原方法不同的特徵簽名(方法引數集合),因為特徵簽名不包括返回值,故Java語言中不能只依靠返回值的不同對一個已有的方法過載;可是在Class檔案格式中。特徵簽名即為方法描寫敘述符,只要是描寫敘述符不全然同樣的2個方法也能夠合法共存。即2個除了返回值不同之外全然同樣的方法在Class檔案裡也能夠合法共存
javap工具在後半部分會列出分析完畢的方法(能夠看到和我們的分析結果是一樣的):
- d:\>javap -verbose Test
- ......
- {
- public com.test.Test();
- Code:
- Stack=1, Locals=1, Args_size=1
- 0: aload_0
- 1: invokespecial #10; //Method java/lang/Object."<init>":()V
- 4: return
- LineNumberTable:
- line 3: 0
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 5 0 this Lcom/test/Test;
- public int getM();
- Code:
- Stack=2, Locals=1, Args_size=1
- 0: aload_0
- 1: getfield #18; //Field m:I
- 4: iconst_1
- 5: iadd
- 6: ireturn
- LineNumberTable:
- line 7: 0
- LocalVariableTable:
- Start Length Slot Name Signature
- 0 7 0 this Lcom/test/Test;
- }
8.屬性表集合
在Class檔案、屬性表、方法表中都能夠包括自己的屬性表集合。用於描寫敘述某些場景的專有資訊
與Class檔案裡其他資料項對長度、順序、格式的嚴格要求不同,屬性表集合不要求當中包括的屬性表具有嚴格的順序,而且僅僅要屬性的名稱不與已有的屬性名稱反覆。不論什麼人實現的編譯器可以向屬性表中寫入自定義的屬性資訊。虛擬機器在執行時會忽略不能識別的屬性,為了能正確解析Class檔案,虛擬機器規範中提前定義了虛擬機器實現必須可以識別的9項屬性:
屬性名稱 |
使用位置 |
含義 |
Code |
方法表 |
Java程式碼編譯成的位元組碼指令 |
ConstantValue |
欄位表 |
finalkeyword定義的常量值 |
Deprecated |
類檔案、欄位表、方法表 |
被宣告為deprecated的方法和欄位 |
Exceptions |
方法表 |
方法丟擲的異常 |
InnerClasses |
類檔案 |
內部類列表 |
LineNumberTale |
Code屬性 |
Java原始碼的行號與位元組碼指令的相應關係 |
LocalVariableTable |
Code屬性 |
方法的區域性變數描寫敘述 |
SourceFile |
類檔案 |
原始檔名 |
Synthetic |
類檔案、方法表、欄位表 |
標識方法或欄位是由編譯器自己主動生成的 |
每種屬性均有各自的表結構。
這9種表結構有一個共同的特點,即均由一個u2型別的屬性名稱開始,能夠通過這個屬性名稱來判段屬性的型別
Code屬性:Java程式方法體中的程式碼經過Javac編譯器處理後,終於變為位元組碼指令儲存在Code屬性中。當然不是全部的方法都必須有這個屬性(介面中的方法或抽象方法就不存在Code屬性)。Code屬性表結構例如以下:
型別 |
名稱 |
數量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
max_stack |
1 |
u2 |
max_locals |
1 |
u4 |
code_length |
1 |
u1 |
code |
code_length |
u2 |
exception_table_length |
1 |
exception_info |
exception_table |
exception_table_length |
u2 |
attributes_count |
1 |
attribute_info |
attributes |
attributes_count |
max_stack:運算元棧深度最大值,在方法執行的不論什麼時刻,運算元棧深度都不會超過這個值。虛擬機器執行時依據這個值來分配棧幀的運算元棧深度
max_locals:區域性變量表所需儲存空間,單位為Slot(參見備註四)。
並非全部區域性變數佔用的Slot之和,當一個區域性變數的生命週期結束後。其所佔用的Slot將分配給其他依舊存活的區域性變數使用。按此方式計算出方法執行時區域性變量表所需的儲存空間
code_length和code:用來存放Java源程式編譯後生成的位元組碼指令。code_length代表位元組碼長度,code是用於儲存位元組碼指令的一系列位元組流。
每個指令是一個u1型別的單位元組,當虛擬機器讀到code中的一個位元組碼(一個位元組能表示256種指令,Java虛擬機器規範定義了當中約200個編碼相應的指令)。就能夠推斷出該位元組碼代表的指令。指令後面是否帶有引數,引數該怎樣解釋。儘管code_length佔4個位元組,可是Java虛擬機器規範中限制一個方法不能超過65535條位元組碼指令。假設超過。Javac將拒絕編譯
ConstantValue屬性:通知虛擬機器自己主動為靜態變數賦值,僅僅有被statickeyword修飾的變數(類變數)才幹夠使用這項屬性。
其結構例如以下:
型別 |
名稱 |
數量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
constantvalue_index |
1 |
能夠看出ConstantValue屬性是一個定長屬性,當中attribute_length的值固定為0x00000002,constantvalue_index為一常量池字面量型別常量索引(Class檔案格式的常量型別中僅僅有與基本型別和字串型別相相應的字面量常量,所以ConstantValue屬性僅僅支援基本型別和字串型別)
對非static型別變數(例項變數。如:int a = 123;)的賦值是在例項構造器<init>方法中進行的
對類變數(如:static int a = 123;)的賦值有2種選擇,在類構造器<clinit>方法中或使用ConstantValue屬性。當前Javac編譯器的選擇是:假設變數同一時候被static和final修飾(虛擬機器規範僅僅要求有ConstantValue屬性的欄位必須設定ACC_STATIC標誌,對finalkeyword的要求是Javac編譯器自己增加的要求),而且該變數的資料型別為基本型別或字串型別。就生成ConstantValue屬性進行初始化;否則在類構造器<clinit>方法中進行初始化
Exceptions屬性:列舉出方法中可能丟擲的受查異常(即方法描寫敘述時throwskeyword後列出的異常),與Code屬性平級,與Code屬性包括的異常表不同,其結構為:
型別 |
名稱 |
數量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
number_of_exceptions |
1 |
u2 |
exception_index_table |
number_of_exceptions |
number_of_exceptions表示可能丟擲number_of_exceptions種受查異常
exception_index_table為異常索引集合,一組u2型別exception_index的集合,每個exception_index為一個指向常量池中一CONSTANT_Class_info型常量的索引,代表該受查異常的型別
InnerClasses屬性:該屬性用於記錄內部類和宿主類之間的關係。
假設一個類中定義了內部類。編譯器將會為這個類與這個類包括的內部類生成InnerClasses屬性,結構為:
型別 |
名稱 |
數量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
number_of_classes |
1 |
inner_classes_info |
inner_classes |
number_of_classes |
inner_classes為內部類表集合。一組內部類表型別資料的集合,number_of_classes即為集合中內部類表型別資料的個數
每個內部類的資訊都由一個inner_classes_info表來描寫敘述,inner_classes_info表結構例如以下:
型別 |
名稱 |
數量 |
u2 |
inner_class_info_index |
1 |
u2 |
outer_class_info_index |
1 |
u2 |
inner_name_index |
1 |
u2 |
inner_name_access_flags |
1 |
inner_class_info_index和outer_class_info_index指向常量池中CONSTANT_Class_info型別常量索引,該CONSTANT_Class_info型別常量指向常量池中CONSTANT_Utf8_info型別常量。分別為內部類的全限定名和宿主類的全限定名
inner_name_index指向常量池中CONSTANT_Utf8_info型別常量的索引。為內部類名稱,假設為匿名內部類。則該值為0
inner_name_access_flags類似於access_flags。是內部類的訪問標誌
標誌名稱 |
標誌值 |
含義 |
ACC_PUBLIC |
0x0001 |
內部類是否為public |
ACC_PRIVATE |
0x0002 |
內部類是否為private |
ACC_PROTECTED |
0x0004 |
內部類是否為protected |
ACC_STATIC |
0x0008 |
內部類是否為static |
ACC_FINAL |
0x0010 |
內部類是否為final |
ACC_INTERFACE |
0x0020 |
內部類是否為一個介面 |
ACC_ABSTRACT |
0x0400 |
內部類是否為abstract |
ACC_SYNTHETIC |
0x1000 |
內部類是否為編譯器自己主動產生 |
ACC_ANNOTATION |
0x4000 |
內部類是否是一個註解 |
ACC_ENUM |
0x4000 |
內部類是否是一個列舉 |
LineNumberTale屬性:用於描寫敘述Java原始碼的行號與位元組碼行號之間的相應關係,非執行時必需屬性。會預設生成至Class檔案裡,能夠使用Javac的-g:none或-g:lines關閉或要求生成該項屬性資訊,其結構例如以下:
型別 |
名稱 |
數量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
line_number_table_length |
1 |
line_number_info |
line_number_table |
line_number_table_length |
line_number_table是一組line_number_info型別資料的集合。其所包括的line_number_info型別資料的數量為line_number_table_length。line_number_info結構例如以下:
型別 |
名稱 |
數量 |
說明 |
u2 |
start_pc |
1 |
位元組碼行號 |
u2 |
line_number |
1 |
Java原始碼行號 |
不生成該屬性的最大影響是:1,丟擲異常時,堆疊將不會顯示出錯的行號。2。除錯程式時無法依照原始碼設定斷點
LocalVariableTable屬性:用於描寫敘述棧幀中區域性變量表中的變數與Java原始碼中定義的變數之間的關係。非執行時必需屬性,預設不會生成至Class檔案裡,能夠使用Javac的-g:none或-g:vars關閉或要求生成該項屬性資訊。其結構例如以下:
型別 |
名稱 |
數量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
local_variable_table_length |
1 |
local_variable_info |
local_variable_table |
local_variable_table_length |
local_variable_table是一組local_variable_info型別資料的集合,其所包括的local_variable_info型別資料的數量為local_variable_table_length,local_variable_info結構例如以下:
型別 |
名稱 |
數量 |
說明 |
u2 |
start_pc |
1 |
區域性變數的生命週期開始的位元組碼偏移量 |
u2 |
length |
1 |
區域性變數作用範圍覆蓋的長度 |
u2 |
name_index |
1 |
指向常量池中CONSTANT_Utf8_info型別常量的索引,區域性變數名稱 |
u2 |
descriptor_index |
1 |
指向常量池中CONSTANT_Utf8_info型別常量的索引。區域性變數描寫敘述符 |
u2 |
index |
1 |
區域性變數在棧幀區域性變量表中Slot的位置,假設這個變數的資料型別為64位型別(long或double), 它佔用的Slot為index和index+1這2個位置 |
start_pc + length即為該區域性變數在位元組碼中的作用域範圍
不生成該屬性的最大影響是:1,當其它人引用這種方法時,全部的引數名稱都將丟失,IDE可能會使用諸如arg0、arg1之類的佔位符取代原有的引數名稱,對程式碼執行無影響,會給程式碼的編寫帶來不便。2,除錯時偵錯程式無法依據引數名稱從執行上下文中獲取引數值
SourceFile屬性:用於記錄生成這個Class檔案的原始碼檔名,為可選項,能夠使用Javac的-g:none或-g:source關閉或要求生成該項屬性資訊,其結構例如以下:
型 |
名稱 |
數量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |
u2 |
sourcefile_index |
1 |
能夠看出SourceFile屬性是一個定長屬性,sourcefile_index是指向常量池中一CONSTANT_Utf8_info型別常量的索引。常量的值為原始碼檔案的檔名稱
對大多數檔案,類名和檔名稱是一致的,少數特殊類除外(如:內部類)。此時假設不生成這項屬性。當丟擲異常時,堆疊中將不會顯示出錯誤程式碼所屬的檔名稱
Deprecated屬性和Synthetic屬性:這兩個屬性都屬於標誌型別的布林屬性。僅僅存在有和沒有的差別。沒有屬性值的概念
Deprecated屬性表示某個類、欄位或方法已經被程式作者定為不再推薦使用。可在程式碼中使用@Deprecated註解進行設定
Synthetic屬性表示該欄位或方法不是由Java原始碼直接產生的,而是由編譯器自行加入的(當然也可設