jvm原理(27)Java位元組碼方法表與屬性表深度剖析
上一節說到成員變數,這一節說一下方法表
圖一:
圖二:
圖三:
行號00000120 開始就是方法表的開始,剛開始2個位元組是方法的數量:00 03 是三個方法(無參構造器、變數a的get和set方法)
方法表:
型別 | 名稱 | 數量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
u2 | attributes | attributes_count |
每一個方法都是這樣的一個結構。
00 03後邊是 access_flags (00 01)根據圖二表示的是public的修飾,name_index:00 07 ,descriptor_index:00 08 在常量池裡邊是:[ #7 = Utf8 <init>
然後就是方法的attributes_count 和attributes :
attributes_count :比如方法執行的位元組碼是什麼?方法的行號表是什麼?區域性變量表是什麼?這些資訊的sum。
attributes_count :00 01 ,方法只有一個屬性,但是attribute_info 的結構是什麼呢?為此我們需要知道attribute_info 的結構,如下:
attributes_count :00 01 後邊的2個位元組就是attribute_name_index:00 09(常量池的【#9 = Utf8 Code】).
attributes_length: 00 00 00 38 (十進位制56)
info:是後面的真正的屬性的內容,即方法的位元組碼。
方法的屬性結構:
- JVM預定義了部分attribute,但是編譯器自己也可以實現自己的attribute寫入class檔案
裡, 供執行時使用。
- 不同的attribute通過attribute_name_index來區分。
- Code結構:
code attribute的作用是儲存該方法的結構,如所對應的位元組碼:
Code_attribute{
u2 attribute_name_index; //00 09
u2 attribute_length; // 00 00 00 38
u2 max_stack; //00 02
u2 max_locals; //00 01
u4 code_length; //00 00 00 0A
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];
}
attribute_length: 表示attribute所包含的位元組數,不包含attribute_name_index和attribute_lenght欄位。
max_stack表示這個方法執行的任何時刻所能達到的運算元棧的最大深度。
max_locals表示方法執行執行期間建立的區域性變數的數目,包含用來表示傳入的引數的區域性變數。
code_length表示該方法所包含的位元組碼的位元組數以及具體的指令碼
具體位元組碼即是該方法被調動時,虛擬機器所執行的位元組碼。
code_length是00 00 00 0A(十進位制10),往後數10個位元組:
2AB700012A04B50002B1
如圖:
這是個位元組對應的就是 jclasslib外掛反編譯的如下資訊:
這裡邊有很多助記詞。助記詞和16進位制編碼在jvm裡邊有已經定義好的對映關係。
單擊bytecode裡邊的load0進入oracle的官方網站,load0在doc裡邊對應的是16進位制的0x2a,而在我們的class檔案裡邊也是2A 。
aload_0 = 42 (0x2a)
aload_1 = 43 (0x2b)
aload_2 = 44 (0x2c)
aload_3 = 45 (0x2d)
同樣的invokespecial 在doc裡邊對應的是16進位制是0xb7
invokespecial = 183 (0xb7)
invokespecial 可以簡單理解為呼叫父類的構造方法,invokespecial 是帶有引數的,引數就是invokespecial 後邊的2個位元組描述的資訊:00 01 ,而00 01是常量池裡邊的某個常量,就是:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
MyTest1的父類是Object,MyTest1構造器會首先呼叫父類的構造器,這個和我們對Java學習的理解不謀而合。
然後接下來又是 aload_0 ,以及 iconst_1 助記詞,在doc裡邊有7個:
iconst_m1 = 2 (0x2)
iconst_0 = 3 (0x3)
iconst_1 = 4 (0x4)
iconst_2 = 5 (0x5)
iconst_3 = 6 (0x6)
iconst_4 = 7 (0x7)
iconst_5 = 8 (0x8)
我們的iconst_1對應的1進位制是0x04 。
接下來是putfield,它是帶引數的。
putfield = 181 (0xb5)
putfield 對應的16進位制是0xb5,我們的二進位制檔案也是0xb5,沒有問題,然後B5後邊的是putfield 的引數:00 02 ,00 02在常量池裡邊的是:
2 = Fieldref #3.#21 // com/twodragonlake/jvm/bytecode/MyTest1.a:I
putfield 的作用是 Set field in object 即 賦值,給誰賦值? 給com/twodragonlake/jvm/bytecode/MyTest1.a:I 賦值,賦值為多少?就是推到棧頂的上一個助記詞iconst_1。
之後是 return 助記詞:
return = 177 (0xb1)
return 的1進位制是B1,我們的class檔案當中也是B1。
整個init方法完成了對a的賦值。
接下來的00 00 是異常表:
由於構造方法沒有異常所以是0。
exception_table,這裡存放的是處理異常的資訊
每個exception_table表項由start_pc, end_pc,handler_pc,catch_type組成。
start_pc h=和end_pc表示在code陣列中的從start_pc到end_pc處包含start_pc,不包含end_pc)的指令丟擲的異常會由這個表項來處理。
handler_pc表示處理異常的程式碼的開始處,catch_type表示會被處理的異常型別,它指向量池裡的一個異常類。當catch_type為0時,表示處理所有的異常。
然後是u2 attributes_count; 也就是後邊的: 00 02 ,意味著構造方法有2個屬性,之後的00 0A(十進位制10)是屬性的索引,在常量池裡邊是10號常量:
#10 = Utf8 LineNumberTable
即,行號表:
附加屬性
lineNumberTable:這個屬性用來表示code陣列中的位元組碼和java程式碼行數之間的關係。
這個屬性可以用來在除錯的時候定位程式碼行的行數。這是屬性在除錯的時候比較有用,就是丟擲異常的程式碼的行數。
LineNumberTable_attribute{
u2 attribute_name_index; //00 0A
u4 attribute_length; // 00 00 00 0A
u2 line_number_table_length; // 00 002 表示2個
{
u2 start_pc;
u2 line_number;
} line_number_table[line_number_table_length]
}
attribute_name_index z 是00 0A ,attribute_length是00 00 00 0A ,也即是說往後數10個位元組都是attribute的整體,後邊的10個位元組是:0002 0000 001A 0004 001B 。0002 是長度,既是2個,後邊的0000 001A 0004 001B 2個位元組是一對,0000 對應 001A(十進位制26) ;0004(十進位制4) 對應001B(十進位制27) ;
接著後邊的是00 0B(十進位制11) 對應常量池:
#11 = Utf8 LocalVariableTable
即,區域性變量表。
區域性變量表棧的位元組的長度:00 00 00 0C(十進位制12),即後邊的12個位元組就是區域性變量表的整體,即:0001 0000 000A 000C 000D 0000
首先0001是區域性變數的個數是1 ;
0000 是區域性變數的開始位置(start PC);
000A 是區域性變數的結束位置(length);
000C 是區域性變數對應常量池裡邊的位置是12,即:
#12 = Utf8 this
構造方法為什麼會有this? this,對於例項方法(非static)是預設隱式傳遞進來的。例項方法至少有一個區域性變數,那就是this。
000D是對區域性變數的一個描述;即:
#13 = Utf8 Lcom/twodragonlake/jvm/bytecode/MyTest1;
0000 是jdk1.6加入的用來做校驗檢查的,略過。
到此init方法解析完畢;
剩下的是getA方法:
00 01 是訪問修飾符,是public的。
00 0E 名字索引, 即:#14 = Utf8 getA
00 0F 描述符索引,即:#15 = Utf8 ()I
00 01 是attributes_count,只有一個。
接著是 00 09 : 名字的索引 #9 = Utf8 Code
00 00 00 2F:屬性的長度,十進位制是47,後邊的47個位元組是getA方法Code的整體。
00 00 00 2F 之後是
max_stack : 00 01
max_locals : 00 01 區域性變數的數目 也是1個,既是this。
code_length: 00 00 00 05
往後數5個位元組:
2A(aload_0)
B4(getfield)
00 02 常量 : #2 = Fieldref #3.#21 // com/twodragonlake/jvm/bytecode/MyTest1.a:I
AC ireturn ireturn是返回一個整型。
2AB40002AC 後邊的00 00 是異常表,程式沒有丟擲異常所以是0。
00 02 是屬性個數有2個。
00 0A 第一個屬性指向第十個常量。#10 = Utf8 LineNumberTable
00 00 00 06 屬性長度是6
0001 0000 001E :0001 有一個屬性:偏移量0,對應原始檔的30行。
然後是區域性變量表:00 0B 常量池的 #11 = Utf8 LocalVariableTable
00 00 00 0C 是區域性變量表位元組長度是 13個位元組。
0001 0000 0005 000C 000D 0000
0001 有一個區域性變數。
0000 開始位置0
0005 結束位置5
000C 區域性變數的名字 #12 = Utf8 this
000D 是區域性變數的描述:#13 = Utf8 Lcom/twodragonlake/jvm/bytecode/MyTest1;
0000 檢驗碼
最後還有一個setA方法:
00 01 是public 描述符
00 10 名字索引 #16 = Utf8 setA
00 11 描述符索引 #17 = Utf8 (I)V
00 01 有一個屬性
00 09 屬性是 #9 = Utf8 Code
00 00 00 3E 屬性的長度 是62
00 00 00 3E 是code的如下資訊:
max_stack : 00 02
max_locals : 00 02 ,00 02 區域性變數的數目 也是2個,既是this和int a
code_length : 00 00 00 06 往後數6個位元組:
2A 1B B5 00 02 B1
laload_0 2A
iload_1 1B
iload_0 = 26 (0x1a)
iload_1 = 27 (0x1b)
iload_2 = 28 (0x1c)
iload_3 = 29 (0x1d)
iload_1的引數(給誰賦值):00 02 【#2 = Fieldref #3.#21 // com/twodragonlake/jvm/bytecode/MyTest1.a:I】
B1 return
2A 1B B5 00 02 B1 後邊的 00 00 是異常表的長度為0。
然後00 02 是方法有2個屬性:
00 0A #10 = Utf8 LineNumberTable
0000 000A 屬性表長度是10個位元組。
即:
0002 0000 0022 0005 0023
0002 : 有2個對應關係、
0000 0022 : 偏移量0對應 34行。
0005 0023 : 偏移量5對應 35行。
LineNumberTable 結束.
00 0B #11 = Utf8 LocalVariableTable
LocalVariableTable:
00 00 00 16 :長度, 後面32個位元組是LocalVariableTable的整體。
00 02 : 區域性變量表有2個。
第一個區域性變數:
00 00 :開始位置
00 06 : 長度是6
00 0C : 區域性變數名字 #12 = Utf8 this
00 0D :區域性變數描述 #13 = Utf8 Lcom/twodragonlake/jvm/bytecode/MyTest1;
0000 檢驗碼
第二個區域性變數:
0000 開始位置0
00 06 長度是6
00 05 : 區域性變數名字 #5 = Utf8 a
00 06: 區域性變數描述 #6 = Utf8 I
00 01 : 校驗碼
剩下的數字節碼檔案的attributes:
00 01 是隻有一個屬性
00 12 是第一個屬性名字的索引 十進位制18 : #18 = Utf8 SourceFile
00 00 00 02長度佔據2個位元組。
最後的00 13的就是不1個位元組。
00 13 是 19 = Utf8 MyTest1.java
至此整個class檔案解析完畢。