首先準備材料:
1 Winhex工具下載:http://www.x-ways.net/winhex/
Winihex用來查看class文件
2 JclassLib工具下載:https://github.com/ingokegel/jclasslib
用於結構化展現字節碼文件
3 java命令:javap -C 類文件名/java -verbose 類文件名
D:\zhunode_prac\demo\target\classes\com\zhunode\bytecode>javap -verbose MyTest01.class
Classfile /D:/zhunode_prac/demo/target/classes/com/zhunode/bytecode/MyTest01.class
Last modified 2019-6-8; size 481 bytes
MD5 checksum dc8c25bf7aa8c02cf95d1dc625ae5ee8
Compiled from "MyTest01.java"
public class com.zhunode.bytecode.MyTest01
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // com/zhunode/bytecode/MyTest01.a:I
#3 = Class #22 // com/zhunode/bytecode/MyTest01
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/zhunode/bytecode/MyTest01;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 MyTest01.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/zhunode/bytecode/MyTest01
#23 = Utf8 java/lang/Object
{
public com.zhunode.bytecode.MyTest01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhunode/bytecode/MyTest01;
public int getA();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhunode/bytecode/MyTest01;
public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 9: 0
line 10: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/zhunode/bytecode/MyTest01;
0 6 1 a I
}
SourceFile: "MyTest01.java"
4 準備java文件:com.zhunode.MyTest01.java文件
package com.zhunode.bytecode;
public class MyTest01 {
private int a;
public int getA() {
return this.a;
}
public void setA(int a) {
this.a = a;
}
}
5 常量表數據結構描述符
關於常量表數據結構:可以查看《Java虛擬機規範》,本章是來自Java虛擬機8,來自第四章內容。當然,關於JVM虛擬機規範也可以查看官方文檔。
第二部分:開始查看文件:
利用Winhex工具打開class文件,內容如圖:
現在來逐步認識這份字節碼結構:
首先該文件是一個16進制的文件
對於第一行的內容來說,CA FE BA BE 是一個固定值,CAFE,JAVA標誌,BABE,baby。這個就是魔數,用於標識該文件是Java的二進制文件,JVM在加載class文件時,會通過魔數來檢查該文件是否爲java class文件。
跟着CA FE BA BE 後面的 00 00 00 34 表示小版本號和大版本號,如00 就與下圖的minor version對應,00 34就與major version對應
00 00轉爲10進制=0
00 34轉爲10進制=3*16^1 + 4*16^0 = 52
在說常量池之前,需要注意的是,常量池不是用於存儲常量的,它是一個數據資源庫,用於爲後面的方法表等部分的內容提供資源內容。
後面的00 18 表示常量池的個數:轉爲10進制的數值爲:24。表示常量池中常量的個數爲24,不過因爲常量池中有一個常量爲保留常量,即JVM內部定義的一個常量,所以除去這個常量,常量池中的常量的個數爲24 - 1 = 23。下圖就是通過javap -verbose得到的常量池
常量池的個數已經知道了,那麼接下來的就是常量池的內容了。根據 材料準備 的 常量池數據結構信息可知,tag項用於表示
常量類型,一個用1bit表示。那麼緊接着 00 18的 0A表示tag值爲10 ,那麼對應的常量類型爲:Constant FieldRef info,這部分由三部分內容組成,1bit的index,2bit的用於指向Constant_Class_info類型的索引,2bit用於指向Constant_NameAndType類型的索引。那麼就可以確定第一個常量池中的常量值內容了:0A 00 00 04 00 14該部分就用於描述常量池的第一個常量。
與第一個常量類似:接下來就直接畫出所有的常量的二進制標誌了。
序號 | 二進制表示 | 釋義 | 對應的常量 |
---|---|---|---|
1 | 0A 00 04 00 14 | 00 004 表示第一個index,00 14表示第二個index | 0A對應常量類型:CONSTANT_Methodref_info,詳情見常量信息。 |
2 | 09 00 03 00 15 | 00 03表示第一個index,指向聲明的字段的類或者是Constant_Class_info的索引項,即索引項爲3;00 15表示第二個index,指向字段的描述符的Constant_TypeAndName_info的索引項,索引項爲21 | 09對應的常量類型:Constant FieldRef info |
3 | 07 00 16 | 00 16表示索引項,指的是指向常量限定名索引,索引項爲22 | 07對應的常量類型:Constant_Class_Info |
4 | 07 00 17 | 00 17表示索引項,指的是指向常量限定名索引,索引項爲23 | 07對應的常量類型:Constant_Class_Info |
5 | 01 00 01 61 | 00 01指的是UTF8編碼的字符串佔用的字符數,61 指長度爲length的UTF-8編碼的字符串,轉爲10進制數值爲97 ,表示的數值爲a。 | 01對應的常量類型:Constant_UTF8_Info |
6 | 01 00 01 49 | 00 01指的是UTF8編碼的字符串佔有的字符數,只有一個字符串,49 表示的字符數值爲I。 | 01對應的常量類型:Constant_UTF8_Info |
7 | 01 00 06 3C 69 6E 69 74 3E | 00 06指的是UTF8編碼的字符串佔有的字符數爲6,有6個字符,3C 69 6E 69 74 3E 表示的字符數值爲<init>。 | 01對應的常量類型:Constant_UTF8_Info |
8 | 01 00 03 28 29 56 | 00 03指的是UTF8編碼的字符串佔有的字符數爲3,有3個字符,28 29 56表示的字符數值爲()V。 | 01對應的常量類型:Constant_UTF8_Info |
9 | 01 00 04 43 6F 64 65 | 00 04指的是UTF8編碼的字符串佔有的字符數爲4,有4個字符,43 6F 64 65表示的字符數值爲Code。 | 01對應的常量類型:Constant_UTF8_Info |
10 | 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 | 00 0F指的是UTF8編碼的字符串佔有的字符數爲16,有16個字符,4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65表示的字符數值LineNumberTable。 | 01對應的常量類型:Constant_UTF8_Info |
11 | 01 00 12 4C 4F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 | 00 12指的是UTF8編碼的字符串佔有的字符數爲18,有18個字符,4C 4F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65表示的字符數值LocalVariableTable | 01對應的常量類型:Constant_UTF8_Info |
12 | 01 00 04 74 68 69 73 | 00 04指的是UTF8編碼的字符串佔有的字符數爲4,有4個字符,74 68 69 73表示的字符數值this | 01對應的常量類型:Constant_UTF8_Info |
13 | 01 00 1F 4C 63 6F 6D 2F 7A 68 75 6E 6F 64 65 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 30 31 3B | 00 1F指的是UTF8編碼的字符串佔有的字符數爲32,有32個字符,4C 63 6F 6D 2F 7A 68 75 6E 6F 64 65 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 30 31 3B表示的字符數值Lcom/zhunode/bytecode/MyTest01; | 01對應的常量類型:Constant_UTF8_Info |
14 | 01 00 04 67 65 74 41 | 00 04指的是UTF8編碼的字符串佔有的字符數爲4,有4個字符,表示的字符數值爲getA | 01對應的常量類型:Constant_UTF8_Info |
15 | 01 00 03 28 29 49 | 00 03指的是UTF8編碼的字符串佔有的字符數爲3,有3個字符,表示的字符數值爲()I | 01對應的常量類型:Constant_UTF8_Info |
16 | 01 00 04 73 65 74 41 | 00 04指的是UTF8編碼的字符串佔有的字符數爲4,有4個字符,表示的字符數值爲setA | 01對應的常量類型:Constant_UTF8_Info |
17 | 01 00 04 28 49 29 56 | 00 04指的是UTF8編碼的字符串佔有的字符數爲4,有4個字符,表示的字符數值爲(I)A | 01對應的常量類型:Constant_UTF8_Info |
18 | 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 | 00 0A指的是UTF8編碼的字符串佔有的字符數爲10,有10個字符,表示的字符數值爲SourceFile | 01對應的常量類型:Constant_UTF8_Info |
19 | 01 00 0D 4D 79 54 65 73 74 30 31 2E 6A 61 76 61 | 00 0D指的是UTF8編碼的字符串佔有的字符數爲13,有13個字符,表示的字符數值爲MyTest01.java | 01對應的常量類型:Constant_UTF8_Info |
20 | 0C 00 07 00 08 | NameAndTypeInfo常量類型由三部分組成,第二項指向全限定名常量項索引,即第7項索引;第三項指向字符串字面量的索引,即第8八項索引 | 0C對應的常量類型:Constant_NameAndTypeField ,具體信息查看結構類型 |
21 | 0C 00 05 00 06 | NameAndTypeInfo常量類型由三部分組成,第二項指向全限定名常量項索引,即第5項索引;第三項指向字符串字面量的索引,即第6項索引 | 0C對應的常量類型:Constant_NameAndTypeField ,具體信息查看結構類型 |
22 | 01 00 1D 63 6F 6D 2F 7A 68 75 6E 6f 6r 65 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 30 31 | 對應的UTF8類型, 63 6F 6d 2f 7a 68 75 6e 6f 6r 65 2f 62 79 74 65 63 6f 64 65 2f 4d 79 54 65 73 74 30 31表示的數值爲com/zhunode/bytecode/MyTest01 | 01對應的常量類型:Constant_UTF8_INFO ,具體信息查看結構類型 |
23 | 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 | 對應的UTF8類型, 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74表示的數值爲java/lang/Object | 01對應的常量類型:Constant_UTF8_INFO ,具體信息查看結構類型 |
到此爲止,常量表的字節碼就分析完成了,現在就開始看方法表的字節碼。
字節碼Access_Flag訪問標誌
對常量池有初步瞭解之後,我們可以來看看訪問標誌信息了,如下:
訪問標誌信息包括該Class文件是類還是接口,是否被定義成public,是否是abastract,如果是類,是否被聲明爲final。而我們定義的源代碼就知道文件是類而且是public的:
類文件的訪問標誌對應的字節碼信息如下:
對上面的類訪問標誌有了初步認識就繼續往下分析,常量池之後的兩個字節表示訪問標誌,則數兩個字節。
0x0021
對應上面的類訪問修飾符可知,並沒有找到對應的修飾符,這是由於Java的修飾符是可以互相組合的,這裏並沒有窮舉每一種組合,而是爲每一個基本的訪問修飾符定義了一個值,那如果一個類即是public,又是final時候,此時就會取這兩個修飾符的並集:
0x0011 | 0x0010 = 0x0011
// 所以在二進制字節碼中就會記錄“0x0011”,同樣的,回到之前的"0x0021",可以發現是
0x0001 | 0x0020 = 0x0021
所以0x0021:是0x0020和0x0001的並集,表示ACC_PUBLIC與ACC_SUPER。表示該類是public的並能調用父類的。所以在javap -verbose中能看到:
緊接着訪問標誌之後兩個字節則是當前類的類名,如下:
所以往後數兩個字節:
注意:這是一個索引,指向常量池,所以從常量池中進行查找。
緊接着再往後兩個字節則是父類類名:
同樣也在常量池中去找尋:
緊接着父類名稱之後,則是接口信息了:
進一步展開如下:
也就是先數兩個字節來看一下接口的數量:
表示當前類沒有實現接口,既然接口數爲0,則接口名則不會出現了,如下:
字段表
好,往下則是字段表信息,如下:
字段表用於描述類和接口中聲明的變量。這裏包含了類級別變量以及實例變量,但是不包括方法內部聲明的局部變量。
接下來需要了解一下“字段表集合”,這是一個比較複雜的概念,雖說展開字段的信息是這樣:
但是變量表是有自己結構的,就像常量池:
字段表的結構如下:
其中access_flags是訪問標誌符,佔兩個字符;name_index代表字段的名稱索引,佔兩個字符;descriptor_index代表描述符的索引,佔2個字節;這三個信息就可以完整的描述一個字段的信息;attributes_count:屬性個數,可有可無;
用結構體形式描述爲:
field_info{
u2 access_flags; // 0002
u2 name_index; // 0005
u2 descriptor_index; // 0006
u2 attributes_count; // 0000
attribute_info attributes[attributes_count];
}
好,回到二進制文件中來分析一下字段信息,先數2個字節來看一下字段的個數:
表示類中有一個字段,接下來得要根據字段表來看一下該字段的具體信息,首先數2個字節表示修飾符信息,如下:
所以往後數兩個字節唄:
那它代表什麼修飾符呢,上這張表去查一下:
呃~~什麼鬼,木有是0x0002的修飾符嘛,其實它是ACC_PRIVATE,只是未列在此表中,對應源代碼確實是嘛:
接下來兩個字節表示字段名稱索引,如下:
繼續數數:
name_index,索引名,去常量池找:
再往後兩個字節則是描述符的索引:
數兩個字節:
繼續在常量池上找
接下來兩個字節則是屬性數量,如下:
說明該字段木有屬性信息,所以之後的attributes也不可能出現在二進制文件當中了,至此,整個字段信息就分析完了!
方法表結構
這一小節來分析方法表的內容
接着來看一下方法表相關的信息:
所以往下找兩個字節:
0x0003表明有三個方法,這是因爲有一個爲默認構造方法,可以通過Bytecode viewer作爲輔助查看:
接着往下則是方法表相關的信息:
先來看一下表結構:
也就是說每個方法所對應的方法表結構如上,所以接下來看一下第一個方法的信息,往下數2個字節則是access_flags,如下:
這是就需要查看對應的方法訪問修飾符了:
表示是public的方法,接下來四個字節爲方法名字和索引和描述符的索引,一起看:
其對應的常量池信息如下:
也就是第一個方法是構造方法,接着往下則是方法的屬性相關的東東,注意:這個屬性跟方法的變量是兩碼事,如下:
所以往下數兩個字節,看一下方法屬性的個數:
說明有一個方法屬性,而往後兩個字節對是屬性名稱索引,如下:
對應常量池:
Code屬性,對於每一個方法都有一個Code屬性,如下:
{
public com.zhunode.jvm.MyTest01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 8: 0
line 10: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/zhunode/jvm/MyTest01;
public int getA();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zhunode/jvm/MyTest01;
public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 17: 0
line 18: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/zhunode/jvm/MyTest01;
0 6 1 a I
}
SourceFile: "MyTest01.java"
其實就是描述了整個方法的Code信息,Code attribute的作用是保存該方法的結構,如所對應的字節碼:
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 attribute_count;
attribute_info attributes[attribute_count];
}
其中attribute_name_index則是屬性名的索引,也就是Code,接下來還有一些信息下面來解釋一下:
- attribute_length表示attribute所包含的字節數,不包含attribute_name_index和attribute_length字段。
說的是什麼意思呢?比如attribute_length=50個字節,則是指往後的50個字節則爲整個Code屬性的信息,所以往後先數4個字節咱們來看一看長度:
0x0038轉爲10進制後的值爲56,即屬性的長度是56,所以往後56個字節爲具體code屬性信息,如下:
那咱們來jclasslib中來確認一下是否也是顯示的56個字節,如下:
- max_stack表示這個方法運行的任何時刻所能達到的操作數棧的最大深度。
也就是在上面56個字節從頭數2個字節,則是這個操作數棧的最大深度,咱們來看一下:
也就是最大的深度爲2。如javap -verbose所示:
-
max_locals表示方法執行期間創建的局部變量的數目,包含用來表示傳入的參數的局部變量。
再繼續讀2個字節:
也就是局部變量的數目爲1,如javap -verbose所示:
-
code_length表示該方法所包含的字節碼的字節數以及具體的指令碼。具體指令碼既是該方法被調用時,虛擬機所執行的字節碼。
也就是佔10個字節,所以往後數10個字節:
其實這10個字節是對應在jclasslib中的代碼助記符的信息,如下:
所以問題就來了,aload_0不是助記符信息麼,怎麼就能夠跟字節碼文件中的字節對應上呢?所謂助記符其實也就是幫忙我們去記憶的符合,在底層其實也是對應的一個個十六進制的數字的,其它aload_0對應的就是2A這個十六進制數字,憑什麼這麼說?因爲有jclasslib這麼好的工具能幫我們對應上,將鼠標放在助記符上發現是一個可以點的鏈接,點一下“aload_0”發現居然鏈到了oracle的官網上的說明上去了,如下:
所以第一個字節已經分析完了,確實是跟助記符對應上了,接着來分析第二個字節:
而在jsclasslib中對應的第二個助記符是“invokespecial”,點擊鏈到官網看一下:
而它的作用可以理解成就是調用父類的方法,很明顯對於咱們自定義的子類肯定會去調用父類的構造方法,而這個助記符是有參數的:
其實也就是往後的兩個字節就是該助記符所對應的參數,如下:
對應常量池爲:
也就是構造方法嘛,如jsclasslib所示:
接下來繼續往下走一個字節:
對應的code結構中的aload_0,如下:
接下來再往後看一個字節:
對應的就是Code結構中的下一個屬性:
爲啥如此任性呢?因爲點擊查看以下說明就曉了:
爲啥要push一個1呢?實際就是給咋們定義的成員變量a賦值,如下:
可見,該變量的賦值是再默認構造函數中進行的,而不是直接進行賦值的,這也就是分析字節碼文件的好處,可以更加真實的發現底層細節。
接下來再數一個字節:
它對應的方法中Code結構的註記符是:putfield。
點擊看一下官網說明:
接下來的註記符是帶有參數的,所以再往後數兩個字節:
對應的常量池:
也就說給MyTest01的成員變量a賦值爲1。
接下來再數一個字節:
對應的註記符如下:
點擊鏈接:
至此整個構造方法就執行完成了。發現通過分析字節碼也能獲得不少新知識嘛,僅通過這個構造函數的執行流程就能知道了對於我們定義的成員變量原來是再構造方法中進行賦值而非直接賦值的,還是挺有價值的。
好,方法的code分析完成之後,則就得往下進行分析了,先來查看一下結構類型:
也就是再數兩個字節,看一下:
說明該方法木有異常信息,所以接下來的異常表就不會顯示再字節碼文件當中了:
其中關於異常還需要解釋一下:
- exception_table,這裏存放的是處理異常的信息。
- 每個exception_table表項有start_pc、end_pc、handler_pc、catch_type組成。
- start_pc和end_pc表示再code數組中的start_pc到end_pc處(包含start_pc,不包含end_pc)的指令拋出異常會由這個表項來處理。
- handler_pc表示處理異常的代碼的開始處。catch_type表示會處理異常類型,它指向常量池的異常類。當catch_type爲0時,表示處理所有的異常。
接下來就到屬性相關的東東了,如下:
所以往下數2個字節:
說明該方法有兩個屬性,往下數兩個字節則是第一個屬性的名字索引,如下:
也就是對應第10的常量池,爲:
該屬性用來表示code數組中的字節碼和Java代碼行數之間的關係。這個屬性可以用來在調試的時候定位代碼執行的行數。而該屬性的結構爲:
其中attribute_name_index就是常量索引10,接下來4個字節則時屬性的長度attribute_length,如下:
也就是屬性的長度爲10,也是就接下來10個字節則爲LineNumberTable的屬性信息,如下:
看一下jclasslib:
下面具體來分析一下這10個字節,根據結構體來看:
先2個字節表示屬性表有幾對映射,如下:
說明有兩對映射,然後再回到結構體中,每對映射的內容爲:
每對佔4個字節,先看第一對映射:
也就是start_pc=0;line_number=8,對應於jclasslib:
由於咱們源代碼木有構造方法,所以字節碼對應源代碼就在第8行,如下:
接下來看第二對映射:
也就是start_pc=4,line_number=8,對應於jclasslib:
因爲成員變量的賦值是在構造方法中完成的,所以對應第10行代碼:
好,方法的第一個屬性已經完了,接下來以同樣的順序來查看方法的第二個屬性信息了,走2個字節來看屬性名稱索引,如下:
對應第11個常量池索引,如下:
它的結構跟LineNumberTable差不多的,往後數4個字節則是局部變量表所佔的長度:
長度爲12,如jclasslib所示:
然後往後數12個字節則是局部變量的具體信息,首先兩個字節則爲局部變量的個數:如下:
額,構造方法哪來的局部變量呢?好奇快,先不管,先來把其他字節分析完成,再往後四個字節表示start_pc和length,如下:
如jclasslib所示:
接下來則爲局部變量的索引爲0,也就是第一個局部變量:
再往後兩個字節則是局部變量對應常量池的索引,如下:
再接下來兩個字節則是對局部變量的一個描述常量索引,如下:
所以對應jclasslib中可以看到:
那思考一下爲啥構造方法中會有一個this的局部變量呢?我們知道在所有方法中我們都能使用this關鍵字來訪問當前的對象,而從字節層面來講其實this是作爲方法的第一個參數傳進來的,也就是說對於java的一個實例方法而言,最少會有一個this的局部變量存在。
還剩最後兩個字節則爲stackmaptable信息,JDK1.6加入的,主要做校驗檢查的,因爲0嘛,所以後面肯定木有相關的信息了,這裏就直接忽略,如下:
這裏就已經將編譯器生成的默認構造方法的字節相關的分析完成了接下來繼續分析自定義的方法啦,按照順序來講的話,應該是分析getA();
還是按照方法的結構來分析,先回憶一下方法的結構:
method_info{
u2 access_flag;
u2 name_index;
u2 descriptor_index;
u2 attribute_count;
attribute_info attributes[attribute_count];
}
前兩個字節表示訪問修飾符,所以跟着上次分析的位置數兩個字節:
根據方法表修飾符可知,0x0001的修飾符是ACC_PUBLIC,
接着4個字節分別表示方法名稱的索引記憶方法描述符的索引,如下:
所以數4個字節:
對應常量池14、15,如下:
確實如咱們的猜想,第二個方法就是getA(),好,繼續往下分析:
表示方法的屬性個數,所以往下數兩個字節:
所以說明只有一個屬性,接着再數2個字節則表示屬性的名稱索引,如下:
對應常量池:
如之前所述:每個方法都有一個對應的Code屬性, 所以接下來就得來看一下Code屬性結構體了,如下:
接着就是attribute_length了,往下數4個字節
說明整個Code長度爲47個字節,對比一下jclasslib所示:
繼續往下:往下兩個字節爲:
所以往下4個字節爲:
- max_statck表示這個方法運行的任何時刻都能達到的操作數棧的最大深度爲1;
- max_locals表示方法執行期間創建的局部變量的個數爲1,而getA()方法壓根就沒有定義局部變量,根據之前分析的默認構造方法可以得知此局部變量爲編譯器生成的this變量。
繼續往下:
長度爲5,表示往後5個字節則爲對應在jclasslib中代碼註記符的信息,也就是方法的執行體,如下:
第一個字節爲:
剛好對應aload_0,如下:
接着第二個字節
對應註記符爲:
看一下官網對它的解釋:
而且它是有參數的,所以往後兩個字節:
對應常量爲:
所以如jclasslib所看到的:
接下來看最後一個Code的字節,如下:
對應的註記符爲:
查看官網說明:
好,接下來則是異常表的信息,如下:
往後數兩個字節:
因爲該方法木有異常,所以爲0,那異常信息就可以忽略了,如下:
接下來則到屬性相關的東東了,如下:
往後數兩個字節:
說明有兩個屬性,往下數兩個字節則是第一個屬性的名字索引,如下:
查看常量池:
表示的是行號表,所以此時就要看一下行號表的結構體了:
接下來4個字節則是屬性的長度attribute_length,如下:
所以接下來的6個字符則爲該行號表的信息,如下:
看一下jclasslib:
下面具體來分析一下這10個字節,根據結構體來看:
先2個字節表示屬性表有幾對映射,如下:
說明有一對映射,然後再回到結構體中,每對映射的內容爲:
每對映射佔4個字節,所以:
也就是start_pc=0;line_number=7,對應於jclasslib:
對應的源代碼爲:
好,方法的第一個屬性已經完了,接下來以同樣的順序來查看方法的第二個屬性信息了,走2個字節來看屬性名稱索引,如下:
對應的常量表:
它的結構跟LineNumberTable差不多的,往後數四個字節則是局部變量表所佔的長度:
長度爲12,如jclasslib所示:
然後往後數12個字節則是局部變量的具體信息,如下:
首先兩個字節則爲局部變量的個數,如下:
說明有一個局部變量,再往後四個字節表示start_pc和length,如下:
如jclasslib所示:
同時,局部變量的索引爲0,也就是第一個局部變量,如下:
再往後兩個個字節則是局部變量對應常量池的索引,如下:
再接下來兩個字節則是對該局部變量的一個描述常量索引,如下:
即this的索引爲第13行
所以對應jclasslib中可以看到:
從這個分析又能證明,對於實例方法,都會有一個this局部變量存在的。
還剩最後兩個字節則爲stackmaptable信息,JDK1.6加入的,主要做校驗檢查的,因爲0嘛所以後面肯定木有相關的信息了,這裏就直接忽略,如下:
至此getA()方法就已經完全分析完了,接着就是第二個方法setA()了:
所以還得來依據方法表來進行分析:
前兩個字節表示訪問修飾符:
根據方法表修飾符可知,0x0001對應地修飾符爲:ACC_PUBLIC。
接着4個字節分別表示方法名稱地索引以及方法描述符地索引,如下:
對應常量池地16,17,如下:
正如我們所看到的,繼續往下分析:
表示方法的屬性個數,所以往下數兩個字節:
所以說明只有一個屬性,接着再數2個字節則表示屬性的名稱索引,如下:
對應常量池:
是不是對它異常的親切了,每個方法必然會有這個屬性,所以接下來就得來看一下Code屬性結構體了,如下:
接着則是attribute_length了,往下數四個字節:
說明整個Code的長度爲62個字節,對比一下jclasslib所示:
繼續往下:往下兩個字節爲:
所以往後數4個字節:
max_stack表示這個方法運行的任何時刻所能達到的操作數棧的最大深度爲2;max_locals表示方法執行期間創建的局部變量的數目爲2,爲啥這次變爲2個局部變量了呢?因爲第一個是隱式的this,第二個則爲方法的int參數。
繼續往下:
表示該方法所包含的字節碼的字節數以及具體的指令碼,所以往後數4個字節:
長度爲6,所以往後再數6個字節則爲對應在jclasslib中的代碼助記符的信息,也就是方法的執行體,如下:
第一個字節爲:
剛好對應aload_0,如下:
接着第二個字節:
對應助記符爲:
看一下官網對它的解釋:
第三個字節:
對應的註記符爲:
看一下官網對他的解釋:
而且它是有參數的,所以往後數兩個字節:
對應常量池爲:
所以如jclasslib所看到的:
接下來最後一個Code的字節,如下:
對應助記符爲:
至此setA()方法的執行體就已經分析完了。
好,接下來則是異常表的信息,如下:
往後數2個字節:
因爲該方法木有異常,所以爲0,那異常信息就可以忽略了,如下:
接下來則到屬性相關的東東了,如下:
往後數兩個字節:
說明該方法有兩個屬性,往下數兩個字節則是第一個屬性的名字索引,如下:
也就是對應常量池10,爲:
該屬性用來表示code數組中的字節碼和Java代碼行數之間的關係。這個屬性可以用來在調試的時候定位代碼執行的行數。而該屬性的結構爲:
其中attribute_name_index就是常量索引10,接下來數4個字節則是屬性的長度attribute_length,如下:
也就是屬性的長度爲10,也就是接下來10個字節則爲LineNumberTable的屬性信息,如下:
看一下jclasslib:
下面具體來分析一下這10個字節,根據結構體來看:
先2個字節表示屬性表有幾對映射,如下:
說明有兩對映射,然後再回到結構體中,每對映射的內容爲:
每對佔4個字節,先看第一對映射:
也就是start_pc=0;line_number=17,對應於jclasslib:
對應源代碼:
接下來看第二對映射:
也就是start_pc=5;line_number=18,對應於jclasslib:
對應源代碼:
好,方法的第一個屬性已經完了,接下來以同樣的順序來查看方法的第二個屬性信息了,走2個字節來看屬性名稱索引,如下:
對應第11個常量池索引,如下:
它的結構跟LineNumberTable差不多的,往後數四個字節則是局部變量表所佔的長度:
長度爲22,如jclasslib所示:
然後往後數12個字節則是局部變量的具體信息,首先兩個字節則爲局部變量的個數,如下:
先分析第一個局部變量,往後四個字節表示start_pc和length,如下:
如jclasslib所示:
接下則爲局部變量的索引爲0,也就是第一個局部變量,如下
再往後兩個字節則是局部變量對應常量池的索引,如下:
再接下來兩個字節則是對該局部變量的一個描述常量索引,如下:
所以對應jclasslib中可以看到:
還剩最後兩個字節則爲stackmaptable信息,JDK1.6加入的,主要做校驗檢查的,因爲0嘛所以後面肯定木有相關的信息了,這裏就直接忽略,如下:
再來分析第二個局部變量,往後四個字節表示start_pc和length,如下:
接下來則爲局部變量的索引爲1,也就是第二個局部變量:
再接下來兩個字節則是對該局部變量的一個描述常量索引,如下:
如jclasslib:
還剩最後兩個字節則爲stackmaptable信息,JDK1.6加入的,主要做校驗檢查的,這裏就直接忽略,如下:
至此!!所有類中的方法相關的字節碼就全部分析完了,確實夠麻煩,最後則是類的屬性信息瞭如下:
往後兩個字節則表明字節碼屬性的長度:
有一個文件屬性,往後兩個字節則爲屬性名稱的常量池索引,如下:
對應常量池:
再往後四個字節則爲屬性所佔字節的長度:
說明attribute_length佔2個字節,也就是最後剩的兩個字節,如下:
對應常量:
如jclasslib所示:
至此,MyTest01字節碼文件就分析完畢,好繁瑣呀,好無聊呀。。。
文章參考:https://www.cnblogs.com/webor2006/
書本參考:《Java虛擬機規範 Java SE 8版.pdf》