這一次,徹底弄懂 Java 字節碼文件!

2020-12-15 CSDN

作者 | 東升的思考

責編 | 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

聲明:本文為作者投稿,版權歸作者個人所有。

相關焦點

  • 一文讓你明白 Java 字節碼
    從我們寫的java文件到通過編譯器編譯成java字節碼文件(也就是.class文件),這個過程是java編譯過程;而我們的java虛擬機執行的就是字節碼文件。不論該字節碼文件來自何方,由哪種編譯器編譯,甚至是手寫字節碼文件,只要符合java虛擬機的規範,那麼它就能夠執行該字節碼文件。那麼本文主要講講java字節碼文件相關知識。
  • 從1+1=2來理解Java字節碼
    本著複習和鞏固的態度,我決定來編譯一個簡單的類文件來分析Java的字節碼內容,來幫助理解和鞏固Java字節碼知識,希望也對閱讀本文的你有所幫助。類文件後,首先執行命令javac TestJava.java 編譯類文件,生成TestJava.class。
  • 「JAVA」萬字長篇詳述字節碼對象與反射機制完成動態編程
    Java 反射在Java的開發環境中,運行java文件需要使用:java xx.java 命令,運行java命令後,便會啟動JVM,將字節碼文件加載到JVM中,然後開始運行;當運行java命令時,該命令將會啟動一個JVM
  • Java默認構造方法在字節碼的實現
    方法在程序的重要性不言而喻,了解方法在字節碼中的表達能夠使我們開發做到更加心中有數。再看class文件結構上一步已經分析完了class文件中的欄位(field_info)的表達,接下來就是方法數(methods_count)與方法(method_info)結構;方法開始位置上一次分析完欄位的位置在「00 01 00 02 00 05 00 06 00 00」,沒有最後的4個零表示欄位的欄位的attributes_count為0
  • JVM之用Java解析class文件
    java之所以能夠實現跨平臺,便在於其編譯階段不是將代碼直接編譯為平臺相關的機器語言,而是先編譯成二進位形式的java字節碼,放在Class文件之中,虛擬機再加載Class文件,解析出程序運行所需的內容。每個類都會被編譯成一個單獨的class文件,內部類也會作為一個獨立的類,生成自己的class。
  • 一文讀懂JAVA類文件結構
    有些人說了類文件結構我們知道或者不知道沒有什麼作用,如果你持有這種觀點,我覺得需要矯正一下,類文件結構是沒有多大的作用,但是它是我們代碼和java虛擬機之間的橋梁,如果研究透了類文件結構,那麼會對我們研究虛擬機的運行起到至關重要的作用,同樣對於我們研究jvm內存調優能夠帶來很大的幫助。
  • 關於java用字節流和字符流讀取文件的各種情況
    ,後來發現是在用字符流和字節流在讀取各種文件上的差別所導致的。java讀取文件的方式1.字節流讀取:InputStream和OutPutStream,其讀取的方式按字節讀取,這個常用於讀取原始數據。2.字符流讀取:Reader和Writer,按字節讀取數據,java裡面一個字符對應兩個字節。
  • Java程式設計師必備基礎:Java代碼是怎麼運行的?
    java源文件編譯為class字節碼 類加載器把字節碼加載到虛擬機的方法區。 因此,在運行Java程序之前,需要編譯器把代碼編譯成java虛擬機所能識別的指令程序,這就是Java字節碼,即class文件。 所以,Java代碼運行的第一步是:把Java原始碼編譯成.class 字節碼文件。
  • Java讀取和寫入txt文件
    1 問題描述對於java的讀取和寫入txt一直心存疑惑,隨著知識的積累,又重新進行學習,對java的文件讀寫理解更加深刻,在這裡將自己的小小經驗總結分享給大家。下面是大家了解java流的一個基本框架。2 問題分析在java中,java的讀寫操作(輸入輸出)可以用「流」這個概念來表示,輸入和輸出功能是Java對程序處理數據能力的提高, java的讀寫操作又分為兩種:字符流和字節流。Java以流的形式處理數據。流是一組有序的數據序列,根據操作的類型,分為輸入流和輸出流。
  • Java之字節輸入流InputStream的簡單介紹
    java.io.InputStream:字節輸入流,此抽象類是表示字節輸入流的所有類的超類。定義了所有子類共性的方法:1.int read()從輸入流中讀取數據的下一個字節。java.io.FileInputStream extends InputStream,FileInputStream:文件字節輸入流,作用:把硬碟文件中的數據,讀取到內存中使用。
  • 這一次,徹底弄懂 JavaScript 執行機制
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本文的目的就是要保證你徹底弄懂JavaScript的執行機制,如果讀完本文還不懂,可以揍我。
  • Java Web安全 || Java基礎 · Java IO/NIO多種讀寫文件方式
    我們通常讀寫文件都是使用的阻塞模式,與之對應的也就是java.io.FileSystem。java.io.FileInputStream類提供了對文件的讀取功能,Java的其他讀取文件的方法基本上都是封裝了java.io.FileInputStream類,比如:java.io.FileReader。
  • Java File類(文件操作類)詳解
    假設在 Windows 作業系統中有一文件 D:\javaspace\hello.java,在 Java 中使用的時候,其路徑的寫法應該為 D:/javaspace/hello.java 或者 D:\\javaspace\\hello.java。
  • Java第一篇:Java程序入門
    ,後綴名必須為.java 。; }}文件名必須是HelloWorld,保證文件名和類的名字是一致的,注意大小寫。每個字母和符號必須與示例代碼一模一樣。第一個HelloWord 源程序就編寫完成了,但是這個文件是程式設計師編寫的,JVM是看不懂的,也就不能運行,因此我們必須將編寫好的Java源文件 編譯成JVM可以看懂的字節碼文件 。
  • 閱讀class文件的三種姿勢(乾貨)
    這一點的具體信息可參考.class文件中包含了被虛擬機所識別的字節碼指令,看懂字節碼指令是邁向高級架構師的一小步(不積跬步無以至千裡)。本文將介紹幾種如何查看class文件字節碼指令的方法。.class文件中存的是被JVM識別的16進位數,這些16進位數與字節碼指令的關係如下(附Java虛擬機規範官方文檔)
  • java程序編譯後會產生什麼
    java程序編譯後會產生什麼   產生:byte code。Java字節碼是Java源文件編譯產生的中間文件。   java虛擬機是可運行java字節碼的假想計算機 java的跨平臺性也是相對與其他程式語言而言的。先介紹一下c語言的編譯過程吧先是C語言源程序 也就是c的文件經過C編譯程序編譯後,生成windows可執行文件exe文件,然後在windows中執行。
  • 深入JAVA 字節碼驗證 for 循環中 list.size()是否會重複調用?
    接下來我查看了字節碼發現,這裡確實會調用多次list.size()方法,字節碼如下:下面我們來談談具體字節碼指令解析Java二進位指令代碼解析Java源碼在運行之前都要編譯成為字節碼格式(如.class文件),然後由ClassLoader將字節碼載入運行
  • 我們寫的Java代碼是怎麼運行起來的?
    Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就可以在多種平臺上不加修改地運行。代碼編譯成.class後綴的字節碼文件 HelloWorld.class編譯好的「.class"字節碼文件是怎麼運行的?
  • JAVA字節碼的應用實例,長見識了
    雖然許多字節碼很簡單,但上面的上半行中的字節很複雜且具體到Java。字節碼的長度是一到五字節,因此它們的名字Java助記符使用前綴是32位是整數,A為參考地址,S為16位整數(短),和乙8位字節。我們使用是對於一個16位常量的8位常數和II6。
  • 教你徹底學會Java序列化和反序列化
    Java序列化是指把Java對象轉換為字節序列的過程,Java反序列化是指把字節序列恢復為Java對象的過程。反序列化:客戶端重文件,或者網絡中獲取到文件以後,在內存中重構對象。序列化:對象序列化的最重要的作用是傳遞和保存對象的時候,保證對象的完整性和可傳遞性。方便字節可以在網絡上傳輸以及保存在本地文件。