Class 文件結(jié)構(gòu)
Class 文件是一組以 8 位字節(jié)為基礎(chǔ)單位的二進制流溉痢,各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在 Class 文件中嘱兼,中間沒有添加任何分隔符凹炸。
1 ClassFile 結(jié)構(gòu)
(從上往下順序)
數(shù)據(jù)類型 | 定義 | 說明 |
---|---|---|
u4 | magic | 魔數(shù)洞渤,固定是 0xCAFEBABE |
u2 | minor_version | 副版本號 |
u2 | major_version | 主版本號 |
u2 | constanct_pool_count | 常量池計數(shù)器 |
cp_info | constanct_pool[constanct_pool_count - 1] | 常量池 |
u2 | access_flags | 訪問標(biāo)志 |
u2 | this_class | 類索引 |
u2 | super_class | 父類索引 |
u2 | interfaces_count | 接口計數(shù)器 |
u2 | interfaces[interfaces_count] | 接口表 |
u2 | fields_count | 字段計算器 |
field_info | fields[fields_count] | 字段表 |
u2 | methods_count | 方法計數(shù)器 |
method_info | methods[methods_count] | 類和接口中定義的所有方法 |
u2 | attributes_count | 屬性計數(shù)器 |
attribute_info | attributs[attributes_count] | 屬性表 |
說明:
- u1 代表1個字節(jié)
- u2 代表2個字節(jié)
- u4 代表4個字節(jié)
- u8 代表8個字節(jié)
1.1 特殊方法
- <init> 這個方法名稱由編譯器生成的雪隧,它代表著類的實例化碍讯,也就是說構(gòu)造函數(shù)的調(diào)用
- <clinit> 這個方法名稱也是由編譯器生成的悬蔽,它代表著靜態(tài)類的調(diào)用
1.2 描述符標(biāo)識字符含義
標(biāo)識符字符 | 含義 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | 特殊類型 void |
L | 對象類型,例如 Ljava/lang/Object |
數(shù)組類型捉兴,每一維將使用一個前置的 "[" 字符來描述屯阀,
例如 String[][] -> [[Ljava/lang/String, int[] -> [i
用描述符描述方法時偿凭,按參數(shù)先西篓,后返回值的順序描述
例如
int indexOf(Sting a, int i) 對應(yīng)是 (Ljava/lang/StringI)I
void inc() 對應(yīng) ()V
2 Magic 魔數(shù) 與版本
源碼
package com.yxhuang.jvm.bytecode;
public class Test1 {
private int a = 1;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
2.1 Magic Number 魔數(shù) u4
位于 Class 文件的頭四個字節(jié)成為魔數(shù)(Magic Number)
作用是確定這個文件能否被虛擬機接受的 Class 文件
上圖中 CA FE BA BE
四個字節(jié)就是 Magice Number 固定不變
2.2 版本
- minor_version 副版本號 u2
- major_version 主版本號 u2
高版本好的虛擬機可以支持低版本號的 Class 文件,反之則不行
例如52.1
52 是主版本號丛忆, 0.1 是副版本號
常用的 Class 文件版本號
編譯器器版本 | 十六進制版本號 | 十進制版本號 |
---|---|---|
JDK 1.7.0 | 00 00 00 32 | 50 |
JDK 1.8.0 | 00 00 00 34 | 52 |
上圖中 00 00 00 34
代表版本號逗栽,副版本號是 00 00
, 主版本號是 00 34
則說明是 JDK 1.8 編譯的
3 常量池 constant_pool
3.1 說明
Class 文件結(jié)構(gòu)中盖袭,只有常量池的計數(shù)是從 1 開始的,其他集合類型都是從 0 開始
圖一中的 contant_pool_count 是 00 18
變成 10 進制是 24,但是常量個數(shù) = 24 -1 = 23 個
3.2 例子
看第一個常量 0x0A
是 10鳄虱,對應(yīng)上表是 CONSTANT_Methodref_info
CONSTANT_Methodref_info{
u1 tag 0A
u2 class_index 00 04
u2 name_and_type_index 00 14
}
- 0A 對應(yīng)是 CONSTANT_Methodref_info 類型
-
00 04 class_index弟塞, 指向常量池表中索引 #4 的 CONSTANT_Class_info 結(jié)構(gòu)。
CONSTANT_Class_info 表示一個類或接口拙已,當(dāng)前字段或方法就是這個類或接口的成員 - 00 14 name_and_type_index决记,指向常量池索引為 #20 的 CONSTANT_NameAndType_info結(jié)構(gòu)。 CONSTANT_NameAndType_info 結(jié)構(gòu)表示當(dāng)前字段或方法的描述符
對著下圖倍踪,我們看到
. #1 對應(yīng) Methodref, 指向 #4.#20系宫, 和我們上面分析的一致
. #4 Class 指向 #23 java/lang/Object 說明當(dāng)前這個方法是 java.lang.Object
. #20 NameAndType 指向了 #7和#8 <init>()V 說明這個方法是 <init>;方法描述符是 ()V
常量池例子圖一
常量池例子圖二
常量池例子圖二 中選中的字節(jié)就是常量池的內(nèi)容
其他的方法可以用類似例子的方法對照常量項的結(jié)構(gòu)總表一個個去分析
常量池中的 14 種常量項的結(jié)構(gòu)總表
3.3 訪問標(biāo)志 access_flags u2
訪問標(biāo)志在常量池之后的后兩位 u2, 用于標(biāo)志類或接口層次的訪問信息建车;
包括
- 這個 Class 是類還是接口
- 是否定義為 public 類型
- 是否定義為 abstract 類型
- 如果是類的話扩借,是否被定義為 final
一般是兩個位的組合
標(biāo)志名稱 | 標(biāo)志值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否為 pulic 類型 |
ACC_FINAL | 0x0010 | 是否聲明為 final,只有類可設(shè)置 |
ACC_SUPER | 0x0020 | 是否允許使用 invokespecial 字節(jié)碼的新語意 |
ACC_INTERFACE | 0x0200 | 標(biāo)志這是個接口 |
ACC_ABSTEACT | 0x0400 | 是否為 sbstract 類型,對于接口或抽象類說缤至,此標(biāo)志值為真潮罪,其他類值為假 |
ACC_SYNTHETIC | 0x1000 | 標(biāo)識這個類并非由用戶代碼產(chǎn)生的 |
ACC_ANNOTATION | 0x2000 | 標(biāo)識這個是注解 |
ACC_ENUM | 0x4000 | 標(biāo)識這個是枚舉 |
如果是一個普通類,不是接口领斥、枚舉或者注解嫉到,被 public 關(guān)鍵字修飾但沒有被聲明為 final 和 abstract.是用組合值
0x0021 = 0x0001|0x0020
說明這個類是 PUBLIC, SUPTER
4 類索引、父索引與接口索引集合
Class 文件由類索引月洛、父索引和接口索引這三項數(shù)據(jù)來確定這個類的繼承關(guān)系屯碴。
4.1 類索引 this_class
類索引用于確定這個類的全限定名
u2 類型的索引值,指向類型 CONSTANT_Class_info 的類描述符
4.2 父索引 super_class
父索引用于確定這個類的父類全限定名
u2 類型的索引值膊存,指向類型 CONSTANT_Class_info 的類描述符
除了 java.lang.Object 外导而,所有的 Java 類的父類索引都不為 0
4.3 接口索引 interfaces
接口索引,入口是 u2 類型的的接口計數(shù)器(interfaces_count)表示索引表的容量隔崎。
如果及計數(shù)器值為 0 今艺,表示這個類沒有任何接口,后面接口的索引表不占任何字節(jié)爵卒。
例子
上圖中在常量池結(jié)束之后虚缎,就是類索引,查看 常量池例子圖一
00 03
指向 #3 的 CONSTANT_Class_info 索引钓株, #3再指向 #20实牡, 是
.#22 = Utf8 com/yxhuang/jvm/bytecode/Test1
說明這個類com.yxhuang.jvm.bytecode.Test1
父類索引是 00 04
, 指向 #4 的 CONSTANT_Class_info 索引轴合,#4 再指向#23
.#23 = Utf8 java/lang/Object
說明這個類的父類是 java.lang.Object创坞。在 java 中如果一個類沒有繼承其他類,它的父類默認(rèn)是 Object, 與我們現(xiàn)在看到的內(nèi)容是對得上的
接口索引計數(shù)器 00 00
說明接口數(shù)量為 0受葛, 則沒有后面的接口索引表
5 字段表 field_info
字段表用于描述接口或類中聲明的變量题涨。
字段(field)包括類級變量(static field)以及實例變量,但不包括在方法內(nèi)部聲明的局部變量偎谁。
字段表中不會列出從超類或者父類接口中繼承而來的字段。
5.1 字段表結(jié)構(gòu)
field_info {
u2 access_flags
u2 name_index
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
- access_flags 用于定義字段的訪問權(quán)限和基本屬性的掩碼標(biāo)識
- name_index 對應(yīng)常量池中的一個有效索引纲堵,指向 CONSTANT_Utf8_info 結(jié)構(gòu)巡雨,標(biāo)識一個有效的字段的非全限定名
- descriptor_index 對應(yīng)常量池中的一個字段描述符索引
- attributes_count 表示當(dāng)前字段的附加屬性數(shù)量
- attribute_info 附加屬性
一般有:- ConstantValue
- Synthetic
- Signature
- Dprecated
- RuntimeVisibleAnnotations
- RuntimeInvisibleAnnoation
表5.1 字段 acces_flags 標(biāo)志列表
標(biāo)志名 | 值 | 說明 |
---|---|---|
ACC_PUBLIC | 0x0001 | 聲明為 public, 可以從包外訪問 |
ACC_PRIVATE | 0x0002 | 聲明為 private, 只能自身訪問 |
ACC_PROTECTED | 0x0004 | 聲明為 protected, 子類可以訪問 |
ACC_STATIC | 0x0008 | 聲明為 static |
ACC_FINAL | 0x0010 | 聲明為 final, 定義后無法修改 |
ACC_VOLATILE | 0x0040 | 聲明為 valatile, 被標(biāo)識的字段無法儲存 |
ACC_TRANSIENT | 0x0080 | 聲明為 transient, 被標(biāo)識的字段不會序列化 |
ACC_SYNTHETIC | 0x1000 | 聲明為被表示的字段的編譯器產(chǎn)生 |
ACC_ENUM | 0x4000 | 聲明為 enum, 枚舉類型 |
descriptor_index 是 1.2 節(jié)中的描述符
attributes 屬性包含 見后面的 屬性 章節(jié)
00 01
是 fields_count,字段個數(shù)席函,說明只有 1 個字段
00 02
是 access_flags铐望,表示是該字段是 private
00 05
是 name_index, 指向 #05 是 a
00 06
是 descriptor_index茂附, 指向 #06 是 I, 根據(jù) 1.2 描述符表說明這個類是 int
00 00
是附加屬性個數(shù)正蛙,0 說明這字段沒有附加屬性
#5 = Utf8 a
#6 = Utf8 I
6 方法 method
所有方法何之,包括實例初始化話方法以及類或接口初始化方法在內(nèi),都是由 methode_info 結(jié)構(gòu)定義的
method_info {
u2 access_flags
u2 name_index
u2 descriptor_index
u2 attributes_count
attribute_info attributes[attributes_count]
}
access_flags 用于定義當(dāng)前方法的訪問權(quán)限和基本屬性的掩碼標(biāo)識
name_index 對應(yīng)常量池中的一個有效索引咽筋,指向 CONSTANT_Utf8_info 結(jié)構(gòu)溶推,標(biāo)識一個有效的字段的非全限定名
descriptor_index 對應(yīng)常量池中的一個字段描述符索引
attributes_count 表示當(dāng)前方法的附加屬性數(shù)量
-
attribute_info 附加屬性
包含:
- Code
- Exceptions
- Synthetic
- Signature
- Dprecated
- RuntimeVisibleAnnotations
- RuntimeInvisibleAnnotations
- RuntimeVisibleParameterAnnotations
- RuntimeInvisibleParameterAnnotations
- AnnotationDefaultjvm_class_10_1.png
00 03
是 method_count ,說明方法的入口說明有三個方法,對照我們的源碼奸攻,我們定義了 getA
setA
兩個方法蒜危,另外就是編譯器自動為當(dāng)前類生成的無參構(gòu)造函數(shù)方法
00 01
是 access_flags, 對照方法訪問標(biāo)志表睹耐,說明該方法是 public
00 07
是 name_index辐赞, 指向常量池 #7 ,是<init>
00 08
是 descriptor_index硝训, 指向常量池的 #8, 是 ()V
00 01
是 attributes_count响委, 附加屬性個數(shù)是 1
00 09
是 attribute_info, 附加屬性的指向索引 #9窖梁,是 Code, 關(guān)于 Code 可以看 第七節(jié)屬性
7 Code 屬性
Code 屬性是變長屬性赘风,位于 method_info 結(jié)構(gòu)的 attribute_info
中。
一個 Code 屬性只為唯一一個方法纵刘、實例初始化方法邀窃、類或接口初始化方法保存 Java 虛擬機指令及其相關(guān)輔助信息。
7.1 格式
Code_attribute {
u2 attribute_name_index; // 一般是 'Code'
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 attributes_count;
attribute_info attributes[attributes_count];
}
- attribute_name_index 是 屬性名稱的的常量索引 假哎,值一般是 Code
- attribute_length 屬性的的長度
- max_stack 代表了操作數(shù)棧(Operand Stacks)深度的最大值瞬捕。在方法執(zhí)行的任意時刻,操作棧都不會超過這深度舵抹。
- max_locals 代表了局部變量表所需的存儲空間肪虎。存儲單位是 Slot
- code_length 代表編譯之后的字節(jié)碼指令長度, u4 類型惧蛹,說明一個方法的最大長度是
2^32 - 1
,也就是 65535 - code[] 當(dāng)前方法的 java 虛擬機的實際字節(jié)碼笋轨, code 用于存儲字節(jié)碼指令的一系列字節(jié)流秆剪。 每個指令是 u1 類型, 根據(jù)對應(yīng)的值去查虛擬機指令表
- exception_table_length 異常表的成員個數(shù)
- exception_table[] 異常表
- start_pc 和 end_pc 表示異常處理的索引范圍爵政,既 start_pc ≤ x < end_pc
- handle_pc 異常處理器的起點
- catch_type 異常類型仅讽,指向常量池中的索引
- attributes_count 表示 Code 中的附加屬性個數(shù)
- attribute_info 附加屬性
7.2 具體的例子
00 09
在上面的 第 6 節(jié)已經(jīng)說明了,是 #9, 值是 Code
00 00 00 38
是 attribute_length 是 Code 屬性長度钾挟,變成10進制是 56 個字節(jié)
00 02
是 max_stack洁灵,說明操作棧是的最大深度是 2
00 01
是 max_locals,說明存儲空間是 1
00 00 00 0A
是 code_length 說明 code 的長度是10個字節(jié)
7.2.1 Code 的指令
下面的內(nèi)容是具體的 Code, 10 個字節(jié)掺出, 每個字節(jié)可以根據(jù)虛擬機字節(jié)碼指令表查找到,關(guān)于指令的具體行為徽千,查看另外的資料
2A B7 00 01 2A 04 B5 00 02 B1
-
2A
是 aload_0 指令 -
B7
是 invokespecial指令, 調(diào)用父類的構(gòu)造方法汤锨, 格式是invokespecial indexByte1 indexByte2
-
00 01
指向常量池的 #1双抽,是java/lang/Object."<init>":()V
-
2A
是 aload_0 指令 -
04
是 iconst_1 指令,將 int 型 1 推送至棧頂 -
B5
是 putfield 是為指定的的類的實例賦值闲礼, 格式是putfield indexbyte1 indexbyte2
-
00 02
指向常量池的 #2 是
Fieldref #3.#21 // com/yxhuang/jvm/bytecode/Test1.a:I
-
B1
是 return 指令
通過 javap -verbose 看到的牍汹, 是一樣的
7.2.2 Code 異常表
根據(jù) Code 結(jié)構(gòu),在 Code 中跟在指令后面的是異常表
00 00
是 exception_table_length 異常表的長度柬泽,這里是 0, 說明這個方法沒有異常
7.2.3 Code 的附近屬性 attribute_info
根據(jù) Code 結(jié)構(gòu)慎菲,在 Code 中跟在異常表后面的是附近屬性
00 02
是 attributes_count 代表這個方法的附近屬性,這里是兩個
第一個附加屬性
00 0A
是指向常量池的 #10, 是 LineNumberTable锨并,說明這個屬性是 LineNumberTable, 關(guān)于 LineNumberTable 屬性露该,可以看后面的第 8 節(jié)
00 00 00 0A
是 attribute_length, 表示附近附加屬性的字節(jié)長度第煮,只是是 10 個字節(jié)解幼, 即后面的 00 02 00 00 00 03 00 04 00 05
00 02
是 line_number_table_length,表示 line_number 數(shù)組的個數(shù)包警,2 個
第一個數(shù)組
00 00
是 start_pc书幕,表示字節(jié)碼行號, 這里是 0
00 03
是 line_number揽趾, 表示 Java 源代碼行數(shù)台汇,這里是 3
第二個數(shù)組
00 04
表示字節(jié)碼行數(shù)是 4
00 05
表示 Java 源代碼行數(shù)是 5
對照下圖,是和我們分析的一樣
第一個屬性已經(jīng)結(jié)束了篱瞎,來看看第二個屬性
第二個屬性
00 0B
指向常量池的 #11 是 LocalVariableTable苟呐,是局部變量表,具體信息看看第 9 節(jié)
00 00 00 0C
表示這個附加屬性的長度是 14 個字節(jié),即 00 01 00 00 00 0A 00 0C 00 0D 00 00
00 01
是 local_variable_table_length, 表示局部變量表的個數(shù)是 1
00 00
是 start_pc 字節(jié)碼行數(shù) 0
00 0A
是 length俐筋,表示當(dāng)前屬性的長度牵素,是 10
00 0C
是 name_index,表示局部變量的名稱澄者,指向常量池的索引 #12笆呆,是 this
00 0D
是 descriptor_index,表示局部變量的描述符请琳,指向常量池的索引 #13,是 Lcom/yxhuang/jvm/bytecode/Test1;
00 00
是 index赠幕,表示這個局部變量在棧幀局部變量表中 Slot 的位置俄精,是 0
從上面的分支,和下圖的結(jié)果可以看到是一樣的
其他剩下的兩個方法都可以按照這樣去分析
8 LineNumberTable 屬性
是可選榕堰、變長屬性竖慧,位于 Code 結(jié)構(gòu)中。
是用來確定源文件中的行號
8.1 結(jié)構(gòu)
LineNumberTable_attribute {
u2 attribute_name_index; // 一般是 `LineNumberTable`
u4 attribute_length;
u2 line_number_table_length;
{
u2 start_pc;
u2 line_number;
}line_number_table[line_number_table_length];
}
- attribute_name_index 指向常量池中的索引逆屡,是 “LineNumberTable”
- attribute_length 表示當(dāng)前屬性的長度
- line_number_table_length 表示數(shù)組的成員的個數(shù)
- start_pc 表示改字符在源文件中的字節(jié)碼行數(shù)
- line_number 表示這個值在源文件的行數(shù)
9 LocalVaribleTable 屬性
是可選圾旨、變長屬性,位于 Code 結(jié)構(gòu)中
確定在執(zhí)行過程中給定局部變量的信息
結(jié)構(gòu)
LocalVaribleTable_attribute {
u2 attribute_name_index; // 一般是`LocalVaribleTable`
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]
}
- attribute_name_index 是指向常量池中的索引魏蔗,是 "LocalVaribleTable"
- attribute_length 屬性的長度
- local_variable_table_length 局部變量表的長度
- start_pc 和 length砍的,表示所有給定的局部變量的索引都在范圍[start_pc, start_pc + length)
- index 表示一個局部變量的有效的非全限定名,指向常量池表的索引
- descriptor_index 表示源程序中局部變量類型的字段描述符莺治,指向常量池的索引
- index 表示此局部標(biāo)量在當(dāng)前棧幀的局部變量表中的索引
10 屬性 attribute
屬性在 Class 文件格式中 ClassFile 結(jié)構(gòu)廓鞠、 field_info 結(jié)構(gòu)、 method_info 結(jié)構(gòu) 和 Code_attribute 結(jié)構(gòu)中
通用格式
attribute_info{
u2 attribute_name_index
u4 attribute_length
u2 info[attribute_length]
}
11 字節(jié)碼文件的 Attribute
00 01
代表屬性個數(shù)产雹, attributes_count诫惭, 只有一個
00 12
是 attribute_name_index翁锡, 表示屬性名稱蔓挖,指向 #18,是 SoureFile
00 00 00 02
是 attribute_length馆衔, 表示屬性長度瘟判,是 2
00 13
是 attribute_info 的信息,指向 #19角溃,是 Test1.java
上面說明拷获,這個屬性名稱是 SourceFile, 名稱是 Test1.java
12 總結(jié)
至此,我們整個 Class 文件已經(jīng)分析完成了减细。分析 Class 文件需要耐心匆瓜,按照 Class 文件結(jié)構(gòu),一個字節(jié)碼未蝌,一個字節(jié)碼去分析驮吱,就可以將整個 Class 文件弄清楚。
如果弄懂了整個 class 文件萧吠,對于學(xué)習(xí) java 或者 kotlin 都有很大的幫助左冬。``