作為一個程序猿,只知道怎么用是遠遠不夠的。起碼,你須要知道為什么能夠這么用。即我們所謂底層的東西。
那究竟什么是底層呢?我認為這不能一概而論。以我如今的知識水平而言:對於Web開發人員,TCP/IP、HTTP等等協議可能就是底層;對於C、C++程序猿。內存、指針等等可能就是底層的東西。那對於Java開發人員。你的Java代碼執行所在的JVM可能就是你所須要去了解、理解的東西。
我會在接下來的一段時間,和讀者您一起去學習JVM。全部內容均參考自《深入理解Java虛擬機:JVM高級特性與最佳實踐》(第二版),感謝作者。
系列文章第一篇:JVM系列文章(一):Java內存區域分析。
系列文章第二篇:JVM系列文章(二):垃圾回收機制。
一、概述
不論什么一個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; } }

1.魔數
非常多文件存儲標准中都使用魔數來進行身份識別。譬如圖片格式gif、jpeg等。使用魔數而不是拓展名來進行識別主要是基於安全方面的考慮,由於文件拓展格式能夠任意修改。

2.版本
3.常量池

類型 |
簡單介紹 |
項目 |
類型 |
描寫敘述 |
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.字段表集合

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源代碼直接產生的,而是由編譯器自行加入的(當然也可設置訪問標志中的ACC_SYNTHETIC標志。全部由非用戶代碼產生的類、方法和字段都應當至少設置Synthetic屬性和ACC_SYNTHETIC標志位中的一項,唯一的例外是實例構造器<init>和類構造器<clinit>方法)
這兩項屬性的結構為(當然attribute_length的值必須為0x00000000):
類型 |
名稱 |
數量 |
u2 |
attribute_name_index |
1 |
u4 |
attribute_length |
1 |

起始2位為0x0001。說明有一個類屬性。接下來2位為屬性的名稱,0x0014,指向常量池中第20個常量:SourceFile。
接下來4位為0x00000002,說明屬性體長度為2字節。最后2個字節為0x0014。指向常量池中第21個常量:Test.java。即這個Class文件的源代碼文件名稱為Test.java
PS:
1,全限定名:將類全名中的“.”替換為“/”,為了保證多個連續的全限定名之間不產生混淆,在最后加上“;”表示全限定名結束。
比如:"com.test.Test"類的全限定名為"com/test/Test;"
2,簡單名稱:沒有類型和參數修飾的方法或字段名稱。比如:"public void add(int a,int b){...}"該方法的簡單名稱為"add","int a = 123;"該字段的簡單名稱為"a"
3。描寫敘述符:描寫敘述字段的數據類型、方法的參數列表(包含數量、類型和順序)和返回值。依據描寫敘述符規則,基本數據類型和代表無返回值的void類型都用一個大寫字符表示,而對象類型則用字符L加對象全限定名表示
標識字符 |
含義 |
B |
基本類型byte |
C |
基本類型char |
D |
基本類型double |
F |
基本類型float |
I |
基本類型int |
J |
基本類型long |
S |
基本類型short |
Z |
基本類型boolean |
V |
特殊類型void |
L |
對象類型,如:Ljava/lang/Object; |
對於數組類型,每一維將使用一個前置的“[”字符來描寫敘述,如:"int[]"將被記錄為"[I","String[][]"將被記錄為"[[Ljava/lang/String;"
用描寫敘述符描寫敘述方法時,依照先參數列表,后返回值的順序描寫敘述,參數列表依照參數的嚴格順序放在一組"()"之內。如:方法"String getAll(int id,String name)"的描寫敘述符為"(I,Ljava/lang/String;)Ljava/lang/String;"
4,Slot。虛擬機為局部變量分配內存所使用的最小單位,長度不超過32位的數據類型占用1個Slot,64位的數據類型(long和double)占用2個Slot