作者 | 東升的思考
責編 | Elle
不囉嗦,直接從最最簡單的一段Java原始碼開啟Java整體字節碼分析之旅。
Java 源碼文件
package com.dskj.jvm.bytecode; publicclassMyTest1{privateint a = 1;publicintgetA(){ return a; } publicvoidsetA(int a){ this.a = a; } }
Java字節碼文件
IDEA工具編譯代碼後,Terminal 終端控制臺,進入到生成class文件的目錄下。
執行如下命令:
javap-verbosecom.dskj.jvm.bytecode.MyTest1
生成字節碼文件內容:
Classfile /.../classes/com/dskj/jvm/bytecode/MyTest.class Last modified Jul 31, 2018; size 489 bytes MD5 checksum bdb537edd2d216ea99d6ce529073ee42 Compiled from"MyTest1.java" public classcom.dskj.jvm.bytecode.MyTest minor version: 0 major version: 52# JDK最大版本號 flags: ACC_PUBLIC, ACC_SUPER Constant pool: ##1 = Methodref #4.#20 // java/lang/Object."<init>":()V#2 = Fieldref #3.#21 // com/dskj/jvm/bytecode/MyTest1.a:I#3 = Class #22 // com/dskj/jvm/bytecode/MyTest1#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/dskj/jvm/bytecode/MyTest1;#14 = Utf8 getA#15 = Utf8 ()I#16 = Utf8 setA#17 = Utf8 (I)V#18 = Utf8 SourceFile#19 = Utf8 MyTest1.java#20 = NameAndType #7:#8 // "<init>":()V#21 = NameAndType #5:#6 // a:I#22 = Utf8 com/dskj/jvm/bytecode/MyTest1#23 = Utf8 java/lang/Object { public com.dskj.jvm.bytecode.MyTest1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: iconst_16: putfield #2 // Field a:I9: return LineNumberTable: line 6: 0 line 8: 4 LocalVariableTable: Start Length Slot Name Signature0100this Lcom/dskj/jvm/bytecode/MyTest1; public int getA(); descriptor: ()I flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=10: aload_01: getfield #2 // Field a:I4: ireturn LineNumberTable: line 11: 0 LocalVariableTable: Start Length Slot Name Signature050this Lcom/dskj/jvm/bytecode/MyTest1; public void setA(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=20: aload_01: iload_12: putfield #2 // Field a:I5: return LineNumberTable: line 15: 0 line 16: 5 LocalVariableTable: Start Length Slot Name Signature060this Lcom/dskj/jvm/bytecode/MyTest1;061 a I } SourceFile: "MyTest1.java」
Java字節碼十六進位
Mac作業系統下建議使用 Hex Fiend 工具查看 MyTest1.class 文件的十六進位格式。
十六進位文本如下,便於後續分析使用:
CA FE BA BE 00 00 00 34 00 18 0A 00 04 00 14 09 00 03 00 15 07 00 16 07 00 17 01 00 01 61 01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 1F 4C 63 6F 6D 2F 64 73 6B 6A 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 31 3B 01 00 04 67 65 74 41 01 00 03 28 29 49 01 00 04 73 65 74 41 01 00 04 28 49 29 56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0C 4D 79 54 65 73 74 31 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 1D 63 6F 6D 2F 64 73 6B 6A 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 4D 79 54 65 73 74 31 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 03 00 01 00 07 00 08 00 01 00 09 00 00 00 38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 06 00 04 00 08 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 0B 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 10 00 11 00 01 00 09 00 00 00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 0F 00 05 00 10 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D 00 00 00 00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00 13
Java字節碼結構分析
前面都是鋪墊,來到重磅分析的一節。
Java字節碼整體結構如下圖所示,以下圖示以不同緯度展示了字節碼結構中所包含的關鍵內容。
Java字節碼整體結構圖:
完整的Java字節碼結構圖:
接下來結合十六進位格式的 class 文件,參照 Java字節碼文件來剖析下都包含了哪些內容。
1)4個字節,Magic Number
魔數,值為0xCAFEBABE,這是Java創始人James Gosling制定
2)2+2個字節,Version
包括minor_version和major_version,major_version:1.1(45),1.2(46),1.3(47),1.4(48),1.5(49),1.6(50),1.7(51),1.8(52),1.9(53),1.10(54)
3)2+n個字節,Constant Pool
包括字符串常量、數值常量等
4)2個字節,Access Flags
訪問標記,標記當前的類是public、final、abstract等等,是不是滿足某些特定要求。
5)2個字節,This Class Name
當前類的名字
6)2個字節,Super Class Name
當前類所屬父類的名字
7)2+n個字節,Interfaces
當前類所實現的接口
8)2+n個字節,Fields
欄位表,描述了當前類的欄位的各種各樣的信息
9)2+n個字節,Methods
方法表,當前類所定義的方法,這部分內容相對比以上字節結構是比較不容易理解
因為在我們一個類的定義當中方法是最常見的,方法裡面包含若干的重要信息,包含籤名、訪問修飾符、名字、方法的執行代碼邏輯、返回值等等。
這些方法也是以信息的形式存儲在編譯之後的字節碼class文件當中,接下來,JVM去執行字節碼文件時,當你調用某個特定方法時,JVM才能根據你所編寫的原始碼的意圖去執行字節碼裡的指令。
對於這個方法來說,在JVM中最終是形成一條條指令的去執行的,也就是說在字節碼裡形成的每一條指令對應源碼文件中的每一行原始碼。
這些指令也可以稱作為助記符,比如aload_0,iload_1等。
10)2+n個字節,Attributes
附加屬性
Class字節碼中有兩種數據類型:
字節數據直接量:這是基本的數據類型。共細分為u1、u2、u4、u8四種,分別代表連續的1個字節、2個字節、4個字節、8個字節組成的整體數據。表(數組),是一種複合的數據結構,表是由多個基本數據或其他表,按照既定順序組成的大的數據集合。表是有結構的,它的結構體現在:組成表的成分所在的位置和順序都是已經嚴格定義好的。接下來,我們使用 javap -verbose 命令分析一個字節碼文件時,將會分析該字節碼文件的魔數、版本號、常量池、訪問標記、類信息、類變量、類的成員變量、類的構造方法與類中的方法信息等信息。
4.1 魔數
魔數:所有的.class字節碼文件的前4個字節都是魔數,文件中魔數為:CA FE BA BE,魔數值為固定值:0xCAFEBABE(咖啡寶貝?),這個值的獲得很有「浪漫氣息」,其作用是確定這個文件是否為一個能被虛擬機接受的Class文件。
4.2 版本號
版本號:魔數之後的4個字節為Class文件版本信息,前兩個字節表示minor version(次版本號),後兩個字節表示major version(主版本號)。
這裡的版本號為00 00 00 34,換算成十進位(3 * 16的1次方 + 4 = 52),表示次版本號為0,主版本號為52。
所以,該文件的版本號為:1.8.0。可以通過java -version命令來驗證這一點。Java的版本號是從45開始的,JDK1.0之後大的主版本號線上加1,如JDK1.1(45)、JDK1.2(46)以此類推JDK1.8(52)。
4.3 常量池
常量池(constant pool):緊接著主版本號之後的就是常量池入口。
一個Java類中定義的很多信息都是由常量池來維護和描述的,可以將常量池看作是Class文件的資源倉庫,比如說Java類中定義的方法與變量信息,都是存儲在常量池中。由於常量池中常量的數量是不固定的,故在常量池入口需要放置一項u2類型的數據,代表常量池容量計數值(constant_pool_count)。
這裡的容量計數是從1開始的,十六進位數為:00 18,轉換為十進位為24,代表常量池中有24項常量,索引值範圍1~24。
常量池數組中元素的個數 = 常量池數 - 1(其中0暫時不使用),所以Java字節碼文件中constant_pool中只看到了23項目常量。那為什麼容量計數不從0開始呢?具體原因下一節說明。
常量池中主要存儲兩類常量:
字面量:字面量如文本字符串,Java中聲明為final的常量值等。符號引用:類和接口的全局限定名,欄位的名稱和描述符,方法的名稱和描述符等。4.3.1 常量池總體結構
Java類所對應的常量池主要由常量池數量與常量池數組(常量表)這兩部分共同構成。
常量池數量緊跟在主版本號後面,佔據2個字節;常量池數組緊跟在常量池數量之後。常量池數組與一般的數組不同的是,常量池數組中元素的類型、結構都是不同的,長度當然也就不同;但是,每一種元素第一個數據都是一個u1類型,該字節是個標誌位,佔據1個字節。
JVM在解析常量池時,會根據這個u1類型來獲取元素的具體類型。值得注意的是,常量池數組中元素的個數 = 常量池數 - 1(其中0暫時不使用),目的是滿足某些常量池索引值的數據在特定情況下需要表達「不引用任何一個常量池」的含義;根本原因在於,索引為0也是一個常量(保留常量),只不過它不位於常量表中,這個常量就對應null值;所以,常量池的索引從1而非0開始。
4.3.2 常量池項目類型
Class文件結構中常量池中實際是有14種數據類型的,12~14種數據類型是在JDK1.7之後添加進來的(新增三種類型分別為:CONSTANT_MethodHandle_info、CONSTANT_MethodType_info、CONSTANT_InvokeDynamic_info),主要是為了更好的支持動態語言調用的。但是,最常用如下所列的列出了11種常規的數據類型:
上述常量都是以「CONSTANT」開頭,以「info」結尾的常量名。每一個常量包含的信息的段都是不同的,我們可以根據每一個段自身的起始和結束位置是什麼來進行分析。
抽出兩個代表性的常量進行解析:
CONSTANT_Utf8_info
如果這個tag的值為1,佔1個字節,它就表示的UTF-8編碼的字符串;length,佔2個字節,比如length值是4,表示的是從length的下後面讀取4個字節長度的字符串。
這個就表示CONSTANT_Utf8_info的具體的文本內容。就是說根據length就能夠知道接下來我要讀取多少個字節才能讀完,這些字節是由bytes來表示的。
CONSTANT_Fieldref_info
tag是U1類型,值為9。有兩個index值,都是U2類型的,第一個index代表的是指向聲明欄位的類或接口描述符CONSTANT_Class_info的索引項,第二個index代表的指向欄位描述符CONSTANT_NameAndType_info的索引項。
具體可以理解為當我們定義一個欄位時,一定是附屬在某一個類上的,所以要先索引到類信息上,可以具體看下CONSTANT_Class_info,其tag是U1類型,值為7,它的index代表指向全限定名常量項的索引,很好理解了。
然後再找到這個欄位的描述符,這裡指向了會索引到CONSTANT_NameAndType_info,其tag是U1類型,值為12,根據兩個index的描述可以理解為要有欄位或方法的名稱以及欄位或方法的描述符即可找到源碼中對應的欄位和方法。
4.3.3 常量池結構分析
接下來,我們以上述Java字節碼結構總表為依據分析下Java字節碼十六進位對應到Java字節碼文件中的constant_pool常量池。
Java字節碼十六進位:
從第9位開始的十六進位
0A 00 04 00 14 0A表示值為10,從字節碼結構總表中找到值為10的是CONSTANT_Methodref_info,有兩個index值,第一個index佔用的字節 00 04 轉換為十進位為4,第二個index佔用的字節00 14 轉化為十進位為20。
從Java字節碼文件中Constant pool定義可看到:
Constant pool: # #1 = Methodref #4.#20// java/lang/Object."<init>":()V
索引到位置#4和#20,從常量池中找到這兩個索引項如下:
#4 = Class#23// java/lang/Object#20 = NameAndType #7:#8// "<init>":()V
這兩個索引正好可以跟結構總表中對應上。其中,#4表示的類全限定名為java/lang/Object,而索引20位置又引用了#7:#8。繼續找到#7和#8:
#7 = Utf8 <init>#8 = Utf8 ()V
就表示類的構造方法,()V表示無參的無返回值的方法描述符。
從第16位開始的十六進位
09 00 03 00 15 這個標誌位值為09,從字節碼結構總表中找到值為9的常量為CONSTANT_Fieldref_info,其後面跟著兩個index,對應十六進位轉換為十進位為3和21。
#2 = Fieldref #3.#21// com/dskj/jvm/bytecode/MyTest1.a:I
對應有兩個索引項#3和#21,如下所示:
#3 = Class#22// com/dskj/jvm/bytecode/MyTest1#21 = NameAndType #5:#6// a:I
索引項#3引用了索引項#22,索引項#21引用了索引項#5:#6
#22 = Utf8 com/dskj/jvm/bytecode/MyTest1#5 = Utf8 a#6 = Utf8 I
根據以上,#5表示的變量名為a,#6表示的變量a的返回類型是I,即int類型的。也就知道了#2 = Fileldref,對應的是com/dskj/jvm/bytecode/MyTest1.a:I。
對應到MyTest1類的變量:
privateint a = 1;
從第21位開始的十六進位
07 00 16 標誌位為07,值為7位元組碼結構總表中對應常量CONSTANT_Class_info,索引佔用2個字節,對應轉換為十進位為22。
#3 = Class#22// com/dskj/jvm/bytecode/MyTest1#22 = Utf8 com/dskj/jvm/bytecode/MyTest1
從第27位開始的十六進位
十六進位字節碼文件:
01 00 01 61 01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
查找標誌位為01 ,值為1的結構總表常量為CONSTANT_Utf8-info,length的佔用2個字節十六進位為 00 01 ,那麼length長度就是1(轉換為十進位的值,即0 * 16的一次方 + 1),後面找到1個字節為61,通過HexFiend工具也能看到指向了a。
所以,找到的十六進位:01 00 01 61
常量池中進一步印證下:
#6 = Utf8 I
十六進位字節碼文件:
01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65...
繼續查找標誌位為01 ,值為1的結構總表常量為CONSTANT_Utf8-info,length的佔用2個字節十六進位為 00 01 ,那麼length長度就是1,後面找到1個字節為49,通過HexFiend工具也能看到指向了I。
所以,找到的十六進位:01 00 01 49
常量池中進一步印證下:
#6 = Utf8 I
十六進位字節碼文件:
010006 3C 696E69743E01000328295601000443 6F64650100 0F 4C 696E654E75 6D 626572546162 6C 65...
繼續查找標誌位為01 ,值為1的結構總表常量為CONSTANT_Utf8-info,length的佔用2個字節十六進位為 00 06 ,那麼length長度就是6(轉換為十進位的值,即0 * 16的一次方 + 6),後面找到6個字節為 3C 69 6E 69 74 3E,通過HexFiend工具也能看到指向了<init>。
所以,找到的十六進位:01 00 06 3C 69 6E 69 74 3E
常量池中進一步印證下:
#7 = Utf8 <init>
以此類推,最終都能通過十六進位字節碼並結合字節碼結構總表分析在常量池中找到對應的字節碼內容。
4.4 訪問標誌
訪問標誌信息包括該Class文件是類還是接口,是否被定義成public,是否是abstract,如果是類,是否被聲明成final。通過上面的MyTest1原始碼,我們知道該文件是類並且是public的。
Access_Flag訪問標誌結構表:
上述MyTest1類十六進位字節碼中的位置:0x 00 21
這個 0x 00 21 是訪問標誌結構表中的 0x 00 20 和 0x 00 01 的併集,表示 ACC_PUBLIC 與 ACC_SUPER。
publicclasscom.dskj.jvm.bytecode.MyTest1 ...flags: ACC_PUBLIC, ACC_SUPER
訪問標誌之後的是This Class Name,對應十六進位為 0x 00 03
在常量池項目類型中查找:
#3 = Class#22// com/dskj/jvm/bytecode/MyTest1
This Class Name之後的是Super Class Name,對應十六進位為 0x 00 04
在常量池項目類型中查找:
#4 = Class#23// java/lang/Object
Interfaces
接口包括兩部分,第一個是interfaces_count(接口個數),第二部分interfaces(接口名)。
當前這個類對應的十六進位:00 00 轉換為十進位仍然是0,說明當前這個類是沒有實現任何接口的。
因此,這個interfaces接口表就不會再出現了。如果接口數量interfaces_count大於等於1的話,那麼這個interfaces接口表是存在的。
4.5 欄位表
Fields
欄位包括兩部分,第一個是fields_count(欄位個數),第二部分fields(欄位名)。
當前這個類對應的十六進位:00 01 轉換為十進位值為1,說明這個類內部有一個欄位。
欄位表集合
欄位表用於描述類和接口中聲明的變量。這裡的欄位包含了類級別變量以及實例變量,但是不包括方法內部聲明的局部變量。
欄位表結構:
第一個是access_flags訪問標誌符,如public、private、protected、final、abstract等等。
第二個name_index和第三個descriptor_index兩個構成一個欄位結構的完整信息。
attributes_count是欄位的獨有的信息,如果值是0,後面的attributes也就不存在了。
具體結構示例:
當前類欄位對應的十六進位如下所示:
field_info { u2 access_flags; 0002 u2 name_index; 0005 u2 descriptor_index; 0006 u2 attributes_counts; 0000 attribute_info attributes[attributes_count];}
0x0002在訪問標誌結構表中對應的是ACC_PRIVATE。
名稱索引 0x0005 與 描述符索引 0x0006 轉換為十六進位為 5 和 6,從 常量池結構表中查找結果:
#5 = Utf8 a#6 = Utf8 I
附加屬性的數量為0x0000,轉換為十進位為0,後面的附加屬性attributes也就不會出現了。
4.6 方法表
0003// methods_count0001// access_flags0007// name_index0008// descriptor_index0001// attributes_count0009// attribute_name_index00000038// attribute_length0002// 附加屬性的 max_stacks0001// 附加屬性的 max_locals0000000A // 附加屬性的 code_length2A B7 00012A 04 B5 0002 B1 // code_lengthc長度的字節,具體執行的字節碼指令00000002000A 0000000A 00020000000600040008000B0000000C 00010000000A 000C 000D 00000001000E 000F000100090000002F00010001000000052A B4 0002 AC 00000002000A 0000000600010000000B000B0000000C 000100000005000C 000D 0000000100100011000100090000003E 00020002000000062A 1B B5 0002 B1 00000002000A 0000000A 00020000000F00050010000B00000016000200000006000C 000D 00000000000600050006000100010012000000020013
Methods
方法包括兩部分,第一個是methods_count(方法個數),第二部分methods(方法名)。
當前這個類對應的十六進位:00 03轉換為十進位值為3,說明這個類內部有三個方法。
三個方法為:
setA()、getA(),以及默認無參的構造方法。
方法表結構:
具體含義類似於上述的欄位表結構。
access_flags 對應的十六進位:00 01 在標誌結構表中查找為ACC_PUBLIC。
name_index名稱索引對應十六進位 00 07 descriptor_index描述符索引對應十六進位 00 08
分別轉換為十進位為 7 和 8,在常量池中查找結果:
#7 = Utf8 <init> // 表示這個類的構造方法#8 = Utf8 ()V // 表示不接收任何參數的不返回結果的描述符
attributes_count對應十六進位:00 01 ,其個數為1,表示會有一個附加屬性。也說明了有一個attributes。
方法的屬性結構構成:
方法中的每一個屬性都是一個atrribute_info結構。
atrribute_info {u2 atrribute_name_index;u4 attribute_length;u1info[atrribute_length];}
attribute_name_index對應十六進位為 00 09,在常量池結構表中查找結果:
#9 = Utf8 Code
從字節碼中每一個方法中都能體現出來,比如默認構造方法:
publiccom.dskj.jvm.bytecode.MyTest1();descriptor: ()Vflags: ACC_PUBLICCode: ...
然後根據 atrribute_length 對應十六進位為 00 00 00 38 轉換為十進位為3 * 16的一次方 + 8 = 56
說明在這個十六進位後面找到56個字節作為Code這個屬性的具體的值。
方法表結構:
前三個欄位和field_info一樣。
method_info {u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count]}
方法的屬性結構:
JVM預定了部分atrribute,但是編譯器自己也可以實現自己的atrribute寫入class文件裡,供運行時使用。不同的attribute通過attribute_name_index來區分。Code結構:
Code attribute的作用是保存該方法的結構,如所對應的字節碼。
attribute_length表示attribute所包含的字節數,不包含atrribute_name_index和attribute_length欄位。max_stack表示這個方法運行的任何時刻所能達到的操作數棧的最大深度。// 00 02max_locals表示方法執行期間創建的局部變量的數目,包含用來表示傳入的參數的局部變量的。// 00 01code_length表示該方法所包含的字節碼的字節數以及具體的指令碼。也即助記符。// 00 00 00 0A 轉換為十進位值為10,即跟著後面的10個字節 2A B7 00 01 2A 04 B5 00 02 B1 這些是字節碼具體指令,對應到構造方法的字節碼:那麼,這些十六進位是怎麼和下面的助記符對應的呢?
我們通過jclasslib工具(字節碼查看工具,支持IDEA插件形式安裝)查看時,點擊助記符的連結會跳到Oracle官網可查看具體詳細解釋。第一個助記符: 0: aload_0 打開連結可以看到:
連結地址:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.aload_n
具體解釋內容所示:
aload_<n> Operation Load reference from local variable Format aload_<n> Forms aload_0 = 42 (0x2a) // 通過這裡就能直接看到 aload_0 對應的十進位是42,轉換為十六進位就是 0x2a,對應字節碼文件中的 2A aload_1 = 43 (0x2b) aload_2 = 44 (0x2c) aload_3 = 45 (0x2d) Description The <n> must be an index into the local variable arrayof the current frame (§2.6). The local variable at <n> must contain a reference. The objectref in the local variable at <n> is pushed onto the operand stack. 這個<n>必須是一個到當前棧幀局部變量數組的一個索引,位於<n>位置上的局部變量會包含一個引用,位於<n>位置上的局部變量的這個引用會被推送到棧頂(準備進行操作)。 第二個助記符:1: invokespecial #1// Method java/lang/Object."<init>":()V 連接地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial invokespecial Operation Invoke instance method; special handling for superclass, private, and instance initialization method invocations Format invokespecial indexbyte1 indexbyte2 Forms invokespecial = 183 (0xb7) Operand Stack ..., objectref, [arg1, [arg2 ...]] → ...
具體字節碼即是該方法被調用時,虛擬機所執行的字節碼。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時,表示處理所有的異常。附加屬性
LineNumberTable:這個屬性用來表示code數組中的字節碼和Java代碼行數之間的關係。這個屬性可以用來在調試的時候定位代碼的執行行數。
LocalVariableTable:局部變量表,當前類中只有唯一的局部變量,而這個局部變量就是this當前對象。
局部變量表屬性類似於行號表屬性。
請注意:
Java原始碼角度:Java類中的實例方法中可以直接使用this。
Java字節碼角度: Java類中的非靜態方法,即實例方法中的這個this實際是通過編譯器隱示的作為方法的第一個參數傳遞進來(有點類似於Python中的方法,其方法中的第一個參數都會傳遞一個self變量,表示當前對象本身)。這樣使得每一個實例方法內部都可以很順利的訪問this。換句話說針對類的實例方法它至少會有一個LocalVariable局部變量,這個變量就是this。
4.7 欄位/方法描述符
在JVM規範中,每個變量/欄位都有描述信息,描述信息主要的作用是描述欄位的數據類型、方法的參數列表(包括數量、類型與順序)與返回值。根據描述符規則,基本數據類型和代表無返回值的void類型都用一個大寫字符來表示,對象類型則使用字符L加對象的全限定名稱來表示。為了壓縮字節碼文件的體積,對於基本數據類型,JVM都只使用一個大寫字母來表示,如下所示:
B - byte,C - char,D - double,F - float,I - int,J - long,S - short,Z - boolean,V - void,L - 對象類型,如Ljava/lang/String;
數組類型: 針對數組類型來說,每一個維度使用一個前置的[來表示,如:
int[]數組被記錄[I,String[][]二維數組被記錄為[[Ljava/lang/String;
方法描述符
用描述符描述方法時,按照先參數列表,後返回值的順序來描述。參數列表按照參數的嚴格順序放在一組()之內,如方法:
String getInfoByIdAndName(int id, String name),該方法的描述符為:(I, Ljava/lang/String;)Ljava/lang/String;
Java字節碼文件的工具推薦:
https://github.com/ingokegel/jclasslib
聲明:本文為作者投稿,版權歸作者個人所有。