官網(wǎng):https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
1. 字節(jié)碼整體結(jié)構(gòu)
1.0 class字節(jié)碼數(shù)據(jù)類型
- 字節(jié)數(shù)據(jù)直接量:這是基本的數(shù)據(jù)類型雇盖,細(xì)分為u1忿等,u2,u4刊懈,u8四種这弧,分別代表1個字節(jié),2個字節(jié)虚汛,4個字節(jié)匾浪,8個字節(jié)組成的整體數(shù)據(jù)
- 表(數(shù)組):表是由多個基本數(shù)據(jù)或其他表,按照既定順序組成的大的數(shù)據(jù)集合卷哩。表示有結(jié)構(gòu)的蛋辈,它的結(jié)構(gòu)體:組成表的成分所在的位置和順序都是已經(jīng)嚴(yán)格定義好的。
1.1 魔數(shù)(Magic)
- magic:u4,魔數(shù)冷溶,代表本文件是.class文件
1.2 版本號(version)
- minor_version:u2渐白,次版本號
- major_version:u2,主版本號逞频,1.6(50) 1.7(51) 1.8(52)
1.3 常量池(constant pool)
constant_pool_count纯衍,u2,常量池個數(shù)
常量池數(shù)組(常量表),n,它與一般的數(shù)組不同的是公给,常量池數(shù)組中不同的元素的類型、結(jié)構(gòu)都是不同的歌亲,長度當(dāng)然也就不同,值得注意的是澜驮,常量池數(shù)組元素的個數(shù)=( constant_pool_count-1 )陷揪,0位暫時不使用。根本原因是杂穷,索引0也是一個常量(保留常量)悍缠,只不過它不位于常量表中,這個常量就對應(yīng)null值亭畜;所以常量池的索引從1開始的
在jvm規(guī)范中扮休,每個變量/字段都有描述信息,描述信息主要作用是描述字段的數(shù)據(jù)類型拴鸵、方法的參數(shù)列表(數(shù)量玷坠、類型與順序)與返回值。
根據(jù)描述符規(guī)則劲藐,基本數(shù)據(jù)類型和代表無返回值的void類型都用一個大寫字符來表示八堡,對象類型則使用字符L加對對象的全限定名稱來表示,jvm都只使用一個大寫字符來表示聘芜,如下所示:B-byte兄渺,C-char,D-double汰现,F(xiàn)-float挂谍,I-int,J-long瞎饲,S-short口叙,Z-boolean,V-void嗅战,L-對象類型妄田,如Ljava/lang/String
對于數(shù)組類型來說俺亮,每個維度使用一個前置的[來表示,如int[]被記錄為[I疟呐,String[][]脚曾,被記錄為[[Ljava/lang/String;
描述符描述方法時,按照先參數(shù)列表启具,后返回值的順序來描述本讥,參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組()之內(nèi),如方法:String getByIdAndName(int id,String name)鲁冯,的描述符:(I, Ljava/lang/String;)Ljava/lang/String
-
常量池通常存兩種常量:
-- 字面量:如字符串囤踩、final修飾的常量等;
-- 符號引用:如類/接口的全限定名晓褪、方法的名稱和描述、字段的名稱和描述等综慎。
-- 常量池每一種元素的第一個數(shù)據(jù)都是一個u1類型涣仿,該字節(jié)是個標(biāo)志位,占據(jù)1個字節(jié)示惊,jvm解析常量池時好港,會根據(jù)這個u1類型來獲取元素的具體類型(如下表),
1.4 訪問控制符
-
access_flags米罚,u2钧汹,主要目的是標(biāo)記該類是類還是接口,訪問權(quán)限是否public录择,abstract拔莱,final
1.5 this class
- u2,常量索引隘竭,用于確定類的全限定名塘秦。
1.6 super class
- u2,的父類索引动看,用于確定直接父類的全限定名
1.7 interfaces
- interfaces_count:u2尊剔,表示當(dāng)前類實現(xiàn)的接口數(shù)量,注意是直接實現(xiàn)的接口數(shù)量菱皆。
- Interfaces:表示接口的全限定名索引须误。每個接口u2,共interfaces_count個
1.8 fields
- fields_count:u2仇轻,表示類變量和實例變量總的個數(shù)京痢。
- fields:fileds的長度為filed_info,filed_info是一個復(fù)合結(jié)構(gòu)拯田,
filed_info: {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
1.9 methods
- methods_count:u2历造,表示方法個數(shù)。
- methods:methods的長度為一個method_info結(jié)構(gòu)
- Java類中的每一個實例方法(非static方法),在編譯生成的字節(jié)碼中吭产,方法的參數(shù)數(shù)量總比源代碼的參數(shù)數(shù)量多一個(this)侣监,它位于方法的第一個參數(shù)位置處:這樣我們就可以在Java的實例方法中使用this來訪問當(dāng)前對象的屬性及方法
- 這個操作是在編譯期間完成的,由javac編譯器在編譯的時候?qū)his的訪問轉(zhuǎn)換為對一個普通實例方法的訪問臣淤,接下在運行期間由jvm在調(diào)用實例方法是橄霉,自動向?qū)嵗椒ㄖ袀魅朐搕his參數(shù),所以在實例方法的局部變量表中邑蒋,至少有一個指向當(dāng)前對象的局部變量
- Java字節(jié)碼對于異常的處理姓蜂,
-- 統(tǒng)一采用異常表的方式來對異常進行處理
-- 在jdk1.4.2之前的版本中,是采用特定的指令方式
-- 當(dāng)異常處理存在finally語句時医吊,現(xiàn)代化的jvm采取的處理方式是將finally語句塊的字節(jié)碼拼接到每一個catch塊的后面钱慢,也就是程序中有多少個catch塊,就會在每一個catch塊后面重復(fù)多少個finally語句塊的字節(jié)碼
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
通用的
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
Code_attribute { //Code_attribute包含某個方法卿堂、實例初始化方法束莫、類或接口初始化方法的Java虛擬機指令及相關(guān)輔助信息
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;//當(dāng)前方法的操作數(shù)棧在方法執(zhí)行的任何時間點的最大深度
u2 max_locals;//分配在當(dāng)前方法引用的局部變量表中的局部變量個數(shù)
u4 code_length;//給出當(dāng)前方法code[]數(shù)組的字節(jié)數(shù)
u1 code[code_length];//給出了實現(xiàn)當(dāng)前方法的Java虛擬機代碼的實際字節(jié)內(nèi)容 (這些數(shù)字代碼實際對應(yīng)一些Java虛擬機的指令)
u2 exception_table_lentgh; //異常的信息
{
u2 start_pc; //這兩項的值表明了異常處理器在code[]中的有效范圍草描,即異常處理器x應(yīng)滿足:start_pc≤x≤end_pc
u2 end_pc; //start_pc必須在code[]中取值览绿,end_pc要么在code[]中取值,要么等于code_length的值
u2 handler_pc; //表示一個異常處理器的起點
u2 catch_type; //表示當(dāng)前異常處理器需要捕捉的異常類型穗慕。為0饿敲,則都調(diào)用該異常處理器,可用來實現(xiàn)finally逛绵。
} exception_table[exception_table_lentgh];
u2 attribute_count; //表示該方法的其它附加屬性怀各,本類有1個
attribute_info attributes[attributes_count]; //LineNumberTable、LocalVariableTable
}
LineNumberTable和LocalVariableTable又是兩個預(yù)定義的attribute术浪,其結(jié)構(gòu)如下:
LineNumberTable_attribute { //被調(diào)試器用來確定源文件中由給定的行號所表示的內(nèi)容渠啤,對應(yīng)于Java虛擬機code[]數(shù)組的哪部分
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{ u2 start_pc;
u2 line_number;//該值必須與源文件中對應(yīng)的行號相匹配
} line_number_table[line_number_table_length];
}
以及:
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{ u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;//表示源程序中局部變量類型的字段描述符
u2 index;
} local_variable_table[local_variable_table_length];
}
1.10 attributes
- attributes_count:u2,這里的attribute表示整個class文件的附加屬性添吗,和前面方法的attribute結(jié)構(gòu)相同
- attributes:class文件附加屬性沥曹,本類中為0017,指向常量池#17碟联,為SourceFile妓美,SourceFile的結(jié)構(gòu)如下:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;//表示本class文件是由ByteCodeTest.java編譯來的
}
2. 案例分析
public class Test1 {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
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 15 4C 63 6F 6D 2F 68 75 69 2F 63 6C 61 7A 7A 2F 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 0A 54 65 73 74 31 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 13
63 6F 6D 2F 68 75 69 2F 63 6C 61 7A 7A 2F 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 07 00 04 00 09 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 0C 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 10 00 05 00 11 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
命令: javap -verbose -p Test1.class
Classfile /Users/zmhui/code/web/target/classes/com/hui/clazz/Test1.class
Last modified 2020-5-17; size 467 bytes
MD5 checksum 3bef9b467a2cb9f695390fa753edc5a1
Compiled from "Test1.java"
public class com.hui.clazz.Test1
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/hui/clazz/Test1.a:I
#3 = Class #22 // com/hui/clazz/Test1
#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/hui/clazz/Test1;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 Test1.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/hui/clazz/Test1
#23 = Utf8 java/lang/Object
{
private int a;
descriptor: I
flags: ACC_PRIVATE
public com.hui.clazz.Test1();
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 7: 0
line 9: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/hui/clazz/Test1;
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 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/hui/clazz/Test1;
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 16: 0
line 17: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/hui/clazz/Test1;
0 6 1 a I
}
SourceFile: "Test1.java"
2.0 對上方class文件進行分析
2.1 魔數(shù)-magic,u4
- CA FE BA BE
2.2 版本號-version鲤孵,u2+u2
- 00 00 00 34
-- 34(16進制)=52壶栋,Java1.8版本
2.3 常量池-constant pool,u2+n
- 00 18
-- 代表:24-1 個常量 - 0A 00 04 00 14
-- 第一個常量:0A=值10普监,查常量表可知常量類型為:constant_methodref_info贵试,可知00 04和00 14都是分別指向常量4和常量20的索引值
-- 即為:#1 = Methodref #4.#20 // java/lang/Object."<init>":()V - 以此方式進行分析
-第一個
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 15 4C 63 6F 6D 2F 68 75 69 2F 63 6C 61 7A 7A 2F 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 0A 54 65 73 74 31 2E 6A 61 76 61
0C 00 07 00 08
0C 00 05 00 06
01 00 13 63 6F 6D 2F 68 75 69 2F 63 6C 61 7A 7A 2F 54 65 73 74 31
01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
-最后一個
2.4 訪問控制符-access flags琉兜,u2
- 00 21,根據(jù)上圖<訪問控制符.jpg>進行查找并沒有00 21值毙玻,
- 訪問標(biāo)志可以是由多個標(biāo)志名稱組成的豌蟋,也就是字節(jié)碼標(biāo)志值00 21可以是多個值進行《或運算》的結(jié)果,由此可以得出 00 21 是ACC_PUBLIC桑滩,ACC_SUPER的或運算結(jié)果梧疲,
2.5 類索引-this class,u2
- 00 03运准, 用來確定這個類的全限定名幌氮,00 03 指向了常量池的第三個常量,
- 即: #3 = Class #22 // com/hui/clazz/Test1
2.6 父類索引-super class胁澳,u2
- 00 04该互,用于確定這個類的父類的全限定名,00 04 指向常量池第四個常量
- 即:#4 = Class #23 // java/lang/Object
2.7 接口-interfaces韭畸,u2+n
- 00 00慢洋,接口索引集合就用來描述類實現(xiàn)了哪些接口,00 00 (interfaces_count)接口數(shù)量陆盘,表示沒有接口
2.8 字段表-fields,u2+n
- 00 01败明,fields_count表示有一個字段
- 00 02 00 05 00 06 00 00隘马,
- 00 02,查詢字段控制符可知為private
- 00 05妻顶,名稱酸员,指向常量5,即:#5 = Utf8 a
- 00 06讳嘱,描述幔嗦,指向常量6,即:#6 = Utf8 I
- 00 00沥潭,屬性數(shù)量邀泉,這里為0
2.9 方法表-methods,u2+n
- 00 03钝鸽,methods_count汇恤,表示有3個方法
第一個方法
- 00 01 00 07 00 08 00 01
- 00 01,訪問控制符查看<方法訪問控制符.jpg>可知為ACC_PUBLIC
- 00 07拔恰,方法名索引因谎,指向常量7,即:#7 = Utf8 <init>
- 00 08颜懊,方法描述索引财岔,指向常量8风皿,即:#8 = Utf8 ()V
- 00 01,表示有一個屬性
第一個屬性
- 00 09匠璧,attribute_name_index指向常量9桐款,即:#9 = Utf8 Code
- 說明此屬性是方法的字節(jié)碼描述Code_attribute,
- 00 00 00 38患朱,attribute_length說明屬性長度為56鲁僚,(不包括前6),即以下56個字節(jié)
- 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 07 00 04 00 09 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00
- 00 02裁厅,max_stack操作數(shù)棧深度的最大值
- 00 01冰沙,max_locals局部變量表所需的存儲空間為 1 個 Slot,max_locals的單位是Slot执虹,Slot是虛擬機為局部變量分配內(nèi)存所使用的最小單位拓挥。
- 00 00 00 0A,code_length生成字節(jié)碼長度袋励,即為10侥啤,那么緊接著10個字節(jié)就是對應(yīng)的數(shù)據(jù),2A B7 00 01 2A 04 B5 00 02 B1茬故,
-- 可以利用idea jclasslib Bytecode viewer插件
-- 讀入2A盖灸,官網(wǎng)查表可知:aload_0 = 42 (0x2a),即aload_0
-- 讀入B7磺芭,官網(wǎng)查表可知:invokespecial = 183 (0xb7)赁炎,作用是以棧頂?shù)膔eference類型的數(shù)據(jù)所指向的對象作為方法接收者,調(diào)用此對象的實例構(gòu)造器方法钾腺、private方法或者它的父類的方法徙垫。后面兩個字節(jié)u2類型的參數(shù)說明具體調(diào)用哪一個方法
-- 讀入00 01,這是invokespecial的參數(shù)放棒,查常量池得0x0001對應(yīng)的常量為實例構(gòu)造器“”方法的符號引用姻报。即:#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
-- 讀入2A,查表可知:aload_0 = 42 (0x2a)间螟,即aload_0
-- 讀入04吴旋,官網(wǎng)查表可知:iconst_1 = 4 (0x4)
-- 讀入B5,官網(wǎng)查表可知:putfield = 181 (0xb5)
-- 讀入00 02厢破,這個是putfield的參數(shù)邮府,查常量池對應(yīng)的常量為: #2 = Fieldref #3.#21 // com/hui/clazz/Test1.a:I
-- 讀入B1,官網(wǎng)
查表可知:return = 177 (0xb1) - 00 00溉奕,exception_table_lentgh褂傀,異常信息這里為0
- 00 02,attribute_count表示兩個屬性
第一個attribute_info:00 0A 00 00 00 0A 00 02 00 00 00 07 00 04 00 09
- 00 0A加勤,attribute_info的attribute_name_index索引仙辟,指向常量池10=LineNumberTable同波,即此屬性表為LineNumberTable,即: #10 = Utf8 LineNumberTable
- 00 00 00 0A叠国,attribute_length即屬性長度為10未檩,
- 00 02 00 00 00 07 00 04 00 09,
- 00 02粟焊,line_number_table_length冤狡,表示有個表信息
- 00 00 00 07,start_pc為0项棠,line_number為7代碼的實際位置
- 00 04 00 09悲雳,start_pc為4,line_number為9
第二個attribute_info:00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00
- 00 0B:attribute_info的attribute_name_index索引香追,指向常量池11=LocalVariableTable
合瓢,即 #11 = Utf8 LocalVariableTable - 00 00 00 0C,attribute_length即12
- 00 01透典,local_variable_table_length即1個
- 00 00晴楔,start_pc
- 00 0A,length峭咒,10
- 00 0C税弃,name_index,即: #12 = Utf8 this
- 00 0D凑队,descriptor_index则果, #13 = Utf8 Lcom/hui/clazz/Test1;
- 00 00,index
第二/三個方法
- 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 0C 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 10 00 05 00 11 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
2.10 類屬性-attributes顽决,u2+n
- 00 01 00 12 00 00 00 02 00 13
- 00 01,attributes_count附加屬性個數(shù)
- 00 12 00 00 00 02 00 13导匣,
- 00 12才菠,attribute_name_index指向 #18 = Utf8 SourceFile
- 00 00 00 02,attribute_length長度2
- 00 13贡定,sourcefile_index指向 #19 = Utf8 Test1.java