1. JVM 概述
JVM
:Java Virtual Machine
,也就是 Java
虛擬機(jī)
所謂虛擬機(jī)是指:通過(guò)軟件模擬的具有完整硬件系統(tǒng)功能的、運(yùn)行在一個(gè)完全隔離環(huán)境中的計(jì)算機(jī)系統(tǒng)。
即:虛擬機(jī)是一個(gè)計(jì)算機(jī)系統(tǒng)。這種計(jì)算機(jī)系統(tǒng)運(yùn)行在完全隔離的環(huán)境中,且它的硬件系統(tǒng)功能是通過(guò)軟件模擬出來(lái)的。
JVM
通過(guò)軟件來(lái)模擬 Java
字節(jié)碼的指令集,是 Java
程序的運(yùn)行環(huán)境。
1.1 JVM 的主要功能
JVM
的主要功能包括:
- 通過(guò)
ClassLoader
尋找和裝載class
文件; - 解釋字節(jié)碼成為指令,并執(zhí)行,同時(shí)提供
class
文件的運(yùn)行環(huán)境; - 進(jìn)行運(yùn)行期間的內(nèi)存分配和垃圾回收;
- 提供與硬件交互的平臺(tái)。
1.2 虛擬機(jī)是 Java 平臺(tái)無(wú)關(guān)的保障
Java
程序只跟 Java
虛擬機(jī)相關(guān),跟平臺(tái)無(wú)關(guān)。
跟平臺(tái)相關(guān)的是
Java
虛擬機(jī)本身。
2. JVM 規(guī)范的作用
Java
虛擬機(jī)規(guī)范為不同的硬件平臺(tái)提供了一種編譯 Java
技術(shù)代碼的規(guī)范。
JVM
規(guī)范只是對(duì)編譯出來(lái)的class
字節(jié)碼文件進(jìn)行規(guī)范,而并沒(méi)有對(duì)Java
源文件進(jìn)行規(guī)范。也就是說(shuō)
Java
虛擬機(jī)只認(rèn)class
字節(jié)碼文件,不認(rèn)Java
源文件。
Java
虛擬機(jī)不關(guān)心class
字節(jié)碼文件是怎么來(lái)的 ,只關(guān)心class
字節(jié)碼文件符不符合JVM
規(guī)范。即使開(kāi)發(fā)語(yǔ)言不是
Java
,只要能編譯生成符合JVM
規(guī)范的class
字節(jié)碼文件,那么這種開(kāi)發(fā)語(yǔ)言也是可以使用的。即
Java
虛擬機(jī)不僅實(shí)現(xiàn)了平臺(tái)無(wú)關(guān),也實(shí)現(xiàn)了開(kāi)發(fā)語(yǔ)言無(wú)關(guān)。
JVM
規(guī)范使 Java
軟件獨(dú)立于平臺(tái),因?yàn)榫幾g是針對(duì)作為虛擬機(jī)的 “一般機(jī)器” 而做。
這個(gè)作為虛擬機(jī)的 “一般機(jī)器” 可以是用軟件模擬并運(yùn)行于各種現(xiàn)存的計(jì)算機(jī)系統(tǒng);也可以是用硬件來(lái)實(shí)現(xiàn)。
JVM
規(guī)范只是對(duì)Java
虛擬機(jī)的實(shí)現(xiàn)提出了一些必須遵循的要求,并沒(méi)有規(guī)定如何去實(shí)現(xiàn)Java
虛擬機(jī)。因此,不同廠商實(shí)現(xiàn)的Java
虛擬機(jī)可能是不同的。
3. JVM 規(guī)范文檔的下載獲取
Java 8
版本的虛擬機(jī)在網(wǎng)上有中文版的。
Java 8
和Java 13
版本的JVM
規(guī)范文檔基本上是一樣的(在Java 13
版本中的第5
章多出了Module
小節(jié))。
4. JVM 規(guī)范中的主要內(nèi)容
這里介紹 Java SE8
虛擬機(jī)規(guī)范文檔(中文版)中的主要內(nèi)容:
- 字節(jié)碼指令集(相當(dāng)于中央處理器
CPU
) -
Class
文件的格式 - 數(shù)據(jù)類型和值
- 運(yùn)行時(shí)數(shù)據(jù)區(qū)
- 棧幀
- 特殊方法
- 類庫(kù)
- 異常
- 虛擬機(jī)的啟動(dòng)、加載、鏈接和初始化
4.1 字節(jié)碼指令集
class
字節(jié)碼指令集的相關(guān)內(nèi)容可參考 《Java
虛擬機(jī)規(guī)范 Java SE 8
版》 中的第 2.11
節(jié)、第 6
章、第 7
章。
詳見(jiàn) 5.
字節(jié)碼指令集
4.2 Class 文件的格式
參考 《Java
虛擬機(jī)規(guī)范 Java SE 8
版》 中第 4
章內(nèi)容
詳見(jiàn) 8. Class
文件的格式
4.3 數(shù)據(jù)類型和值
參考 《Java
虛擬機(jī)規(guī)范 Java SE 8
版》 中第 2.2
、2.3
、2.4
節(jié)內(nèi)容
4.4 運(yùn)行時(shí)數(shù)據(jù)區(qū)
參考 《內(nèi)存分配》 中的 運(yùn)行時(shí)數(shù)據(jù)區(qū)
4.5 棧幀
參考本文章中的 棧幀
參考 《內(nèi)存分配》 中的 Java
棧
參考 《字節(jié)碼執(zhí)行引擎》 中的 棧幀
4.6 特殊方法
參考 《Java
虛擬機(jī)規(guī)范 Java SE 8
版》 中第 2.9
節(jié)內(nèi)容
詳見(jiàn) 6.
特殊方法
4.7 類庫(kù)
詳見(jiàn) 7.
類庫(kù)
4.8 異常
參考 《Java
虛擬機(jī)規(guī)范 Java SE 8
版》 中第 2.10
節(jié)內(nèi)容
4.9 虛擬機(jī)的啟動(dòng)、加載、鏈接和初始化
參考 《類加載、連接和初始化》
5. 字節(jié)碼指令集
5.1 字節(jié)碼指令集簡(jiǎn)介
Java
虛擬機(jī)的指令由一個(gè)字節(jié)長(zhǎng)度的、代表著某種特定操作含義的 操作碼(opcode
)以及跟隨其后的 0
至多個(gè)代表此操作所需參數(shù)的 操作數(shù)(operand
)所構(gòu)成。
虛擬機(jī)中許多指令并不包含操作數(shù),只有一個(gè)操作碼。
我們常說(shuō)的字節(jié)碼指令其實(shí)就是這里的操作碼。
5.2 數(shù)據(jù)類型與指令集
JVM
根據(jù)不同的數(shù)據(jù)類型提供不同的字節(jié)碼指令。
如下表所示:
第 1 列的 Txxx 中的 T 相當(dāng)于泛型參數(shù),
即數(shù)據(jù)類型不同,T 的取值不同。如:對(duì) byte 類型,Tipush 即為 bipush
5.3 加載和存儲(chǔ)指令
5.4 算術(shù)指令
5.5 類型轉(zhuǎn)換指令
5.6 對(duì)象的創(chuàng)建與操作指令
5.7 操作數(shù)棧管理指令
5.8 控制轉(zhuǎn)移指令
5.9 方法調(diào)用和返回指令
5.10 拋出異常相關(guān)的指令
5.11 同步相關(guān)的指令
5.12 指令格式表(描述指令的功能,使用方式等)
通過(guò)指令格式表對(duì)指令進(jìn)行描述,告訴你如何使用指令,指令格式表如下圖所示:
指令格式表中描述了指令的功能,使用方式,注意事項(xiàng)等。
上表中,助記符就是字節(jié)碼指令(助記符是給人看的)。操作碼就是字節(jié)碼指令對(duì)應(yīng)的編碼(給機(jī)器識(shí)別的)。
指令集中的指令都會(huì)以上表為模板進(jìn)行描述。
如何閱讀指令格式表
下面舉例說(shuō)明如何閱讀指令格式表中對(duì)指令的描述。
實(shí)際開(kāi)發(fā)中,可以在 《
Java
虛擬機(jī)規(guī)范Java SE 8
版》 中第6.5
節(jié)中查找具體指令的格式表
5.13 如何用字節(jié)碼指令集表示 Java 代碼
如果對(duì)某些字節(jié)碼指令不知道什么時(shí)候用到,可以參數(shù) 《
Java
虛擬機(jī)規(guī)范Java SE 8
版》 中第3
章的內(nèi)容,查看字節(jié)碼指令對(duì)應(yīng)的Java
代碼是什么樣的。
6. 特殊方法
6.1 <init>
實(shí)例初始化方法,通過(guò) JVM
的 invokespecial
指令 來(lái)調(diào)用
<init>
方法并不是指Java
代碼中定義的構(gòu)造方法。
Java
代碼中定義的構(gòu)造方法可以理解成是JVM
在執(zhí)行完<init>
方法,創(chuàng)建了實(shí)例對(duì)象之后,給程序員提供的一種回調(diào)方法。
6.2 <clinit>
類或接口的初始化方法,不包含參數(shù),返回 void
。
7. 類庫(kù)
8. Class 文件的格式
8.1 Class 文件概述
Class
文件是 JVM
的輸入;是 JVM
實(shí)現(xiàn)平臺(tái)無(wú)關(guān)、語(yǔ)言無(wú)關(guān)的基礎(chǔ)。
Java
虛擬機(jī)規(guī)范中定義了Class
文件的結(jié)構(gòu)(參考 《Java
虛擬機(jī)規(guī)范Java SE 8
版》 中第4
章內(nèi)容)。
Class
文件是一組以 8
字節(jié)為單位的字節(jié)流。文件中的各個(gè)數(shù)據(jù)項(xiàng)目按指定的順序緊湊排列。
對(duì)于占用空間大于
8
字節(jié)的數(shù)據(jù)項(xiàng),按照高位在前的方式分割成多個(gè)8
字節(jié)進(jìn)行存儲(chǔ)。
8.2 Class 文件中的兩種類型(無(wú)符號(hào)數(shù)、表)
總的來(lái)說(shuō),Class
文件中只有兩種類型:
-
無(wú)符號(hào)數(shù): 即基本數(shù)據(jù)類型。以
u1
、u2
、u4
、u8
來(lái)代表幾個(gè)字節(jié)的無(wú)符號(hào)數(shù)。u1
代表1
字節(jié)的無(wú)符號(hào)數(shù),…,u8
代表8
字節(jié)的無(wú)符號(hào)數(shù)。 -
表: 由多個(gè)無(wú)符號(hào)數(shù)和其他表構(gòu)成的復(fù)合數(shù)據(jù)類型。通常以 “
_info
” 結(jié)尾。
8.3 ClassFile 結(jié)構(gòu)
8.4 查看 Java 源碼對(duì)應(yīng)的 Class 字節(jié)碼文件的三種方式
8.4.1 在 Eclipse
中查看 class
文件
在 Eclipse
的 Navigator
視圖中找到 class
文件,打開(kāi)查看即可。
此方式查看的
class
文件不完整。
8.4.2 通過(guò) javap
命令查看 class
文件
在 class
文件所在的 bin
目錄下執(zhí)行 “javap -verbose
全路徑類名” 查看 class
文件。
此方式查看的
class
文件是完整的。
javap
生成的非正式 “虛擬機(jī)匯編語(yǔ)言” 的格式:
通過(guò) javap
查看 class
文件時(shí),對(duì)于類中成員方法的方法體代碼,采用非正式 “虛擬機(jī)匯編語(yǔ)言” 進(jìn)行描述。格式如下:
<index> <opcode> [<operand1> [<operand2> ...]] [<comment>]
其中:
<index> 是指令操作碼在 code[] 數(shù)組中的索引,code[] 數(shù)組以字節(jié)形式來(lái)存儲(chǔ)當(dāng)前分發(fā)的 `Java` 虛擬機(jī)代碼;
也可以是相對(duì)于方法起始處的字節(jié)偏移量
<opcode> 是指令的操作碼
<operand> 是操作數(shù)
<comment> 是行尾的注釋
如上圖中的 main
方法所示:
對(duì)于 0: getstatic #23 // Field java/lang/System.out:Ljava/io/PrintStream;
其中:
<index> 就是 0
<opcode> 就是助記符 getstatic(用于獲取類的靜態(tài)字段值)
<operand> 只有一個(gè),就是 #23(表示 Constant Pool 常量池中的編號(hào))
<comment> 就是 // 后面的內(nèi)容
這條非正式 “虛擬機(jī)匯編語(yǔ)言” 的含義就是:將靜態(tài)字段值 System.out 插入到操作數(shù)棧的棧頂
8.4.3 通過(guò) 16
進(jìn)制文件查看工具(如 winhex
)查看 class
文件
參考 《Java
虛擬機(jī)規(guī)范 Java SE 8
版》 第 4.1
節(jié)內(nèi)容,結(jié)合 ClassFile
結(jié)構(gòu),對(duì) winhex
中顯示的 Hello.class
文件的 16
進(jìn)制數(shù)據(jù)分析如下:
1. 最開(kāi)始的 4 字節(jié)(u4)是 magic(魔數(shù)),JVM 規(guī)范要求固定為 0xCAFEBABE
2. 接下來(lái)的 2 字節(jié)(u2)是 minor_version(副版本號(hào)),即 0x0000,對(duì)應(yīng)十進(jìn)制 0
3. 接下來(lái)的 2 字節(jié)(u2)是 major_version(主版本號(hào)),即 0x0034,對(duì)應(yīng)十進(jìn)制 16*3+4 = 52
因?yàn)?JDK 版本為 1.k(k>=2)時(shí),對(duì)應(yīng)的 class 文件版本號(hào)范圍是 45.0 ~ 44+k.0,所以這里的版本號(hào)是 1.8.0(44+8 = 52)
4. 接下來(lái)的 2 字節(jié)(u2)是 constant_pool_count,表示常量池的大小,即 0x0035,對(duì)應(yīng)十進(jìn)制 53
注意:常量池的大小不是指常量池所占的內(nèi)存字節(jié)數(shù),而是指常量池中的成員個(gè)數(shù)。
常量池中的成員索引為 [0, constant_pool_count - 1],
常量池中的 #0 不是一個(gè)有效的常量池項(xiàng),僅用來(lái)表示 “不引用任何一個(gè)常量池項(xiàng)”。
也就是說(shuō),當(dāng) constant_pool_count = 53 時(shí),常量池中有效的常量索引為 #1 ~ #52
8.5 常量池
Java
虛擬機(jī)指令不依賴于類、接口、類的實(shí)例對(duì)象、或數(shù)組的運(yùn)行時(shí)布局,而是依賴常量池表中的符號(hào)信息。
即通過(guò)指令集中的指令來(lái)描述
Java
代碼時(shí),需要依賴class
文件中常量池內(nèi)保存的符號(hào)信息。
8.5.1 常量池項(xiàng)在 class
文件中的通用格式
常量池表中的所有的項(xiàng)都具有如下通用格式:
cp_info {
u1 tag;
u2 info[];
}
常量池表中,一個(gè) cp_info
表示一個(gè)常量池項(xiàng),ClassFile
結(jié)構(gòu)中的常量池?cái)?shù)組就是由多個(gè) cp_info
構(gòu)成的。
常量池表中,存在多個(gè)不同類型的 cp_info
(常量池項(xiàng))。
cp_info
中占1
字節(jié)(u1)的tag
表示cp_info
的類型。
tag
不同,cp_info
成員結(jié)構(gòu)(info[]
)也不同,cp_info
所占的內(nèi)存大小也不同。
注意:通用格式中的 “
u1 info[]
” 并不是說(shuō)cp_info
中tag
之后info
數(shù)組只占1
字節(jié)。“
u1 info[]
” 沒(méi)有明確的含義,必須根據(jù)不同的 tag 值,才能確定info[]
表示的成員結(jié)構(gòu)和所占的內(nèi)存大小。
8.5.2 常量池項(xiàng)的類型(tag
)
上圖所示的表中給出了
cp_info
常量池項(xiàng)的不同tag
類型,以及對(duì)應(yīng)的tag
數(shù)值。
8.5.2.1 CONSTANT_Class_info
類型
8.5.2.2 CONSTANT_Fieldref_info
類型
8.5.2.3 CONSTANT_Methodref_info
類型
8.5.2.4 CONSTANT_InterfaceMethodref_info
類型
8.5.2.5 CONSTANT_String_info
類型
8.5.2.6 CONSTANT_Integer_info
類型
8.5.2.7 CONSTANT_Float_info
類型
8.5.2.8 CONSTANT_Long_info
類型
8.5.2.9 CONSTANT_Double_info
類型
8.5.2.10 CONSTANT_NameAndType_info
類型
8.5.2.11 CONSTANT_Utf8_info
類型
8.5.2.12 CONSTANT_MethodHandle_info
類型
8.5.2.13 CONSTANT_MethodType_info
類型
8.5.2.14 CONSTANT_InvokeDynamic_info
類型
8.5.3 如何確定 class
文件中常量池的結(jié)束位置
8.6 access_flags(訪問(wèn)權(quán)限標(biāo)志)
8.7 this_class、super_class、interfaces
8.8 字段(成員變量)
每個(gè)字段(field
)都通過(guò) field_info
結(jié)構(gòu)定義。
8.8.1 字段在 class
文件中的格式
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中:
access_flags:表示字段的訪問(wèn)權(quán)限和基本屬性。可以是多個(gè)權(quán)限和屬性的標(biāo)志的組合(位或運(yùn)算)。
name_index:是對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Utf8_info,用于表示字段名。
descriptor_index:是對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Utf8_info,用于表示字段描述符。
attributes_count:表示當(dāng)前字段的附加屬性的數(shù)量。
attributes[]:表示附加屬性表,表中每個(gè)附加屬性的結(jié)構(gòu)必須是 attribute_info。
字段描述符
字段的描述符(descriptor
)是一個(gè)用于 描述字段類型 的字符串。
字段描述符不僅可以描述成員變量的類型,還可以描述靜態(tài)變量和局部變量的類型。
即:字段描述符就是用來(lái)描述任意變量的類型的。
參考 《
Java
虛擬機(jī)規(guī)范Java SE 8
版》 第4.3.2
節(jié)(字段描述符)內(nèi)容。
// 字段描述符用于描述字段類型
FieldDescriptor:
FieldType
// 字段類型包括:基本類型、類類型、數(shù)組類型
FieldType:
BaseType
ObjectType
ArrayType
// 基本類型就是 byte/char/double/float/int/long/short/boolean 其中之一。
BaseType: one of
B C D F I J S Z
// 類類型就是 "L + 全路徑類名(路徑中的 "." 用 "/" 代替) + ;"
ObjectType:
L<ClassName>;
// 數(shù)組類型就是 "[ + 元素類型"
ArrayType:
[<ComponentType>
// 數(shù)組的元素類型就是 字段類型
ComponentType:
FieldType
字段描述符解釋表如下:
FieldType 中的字符 |
類型 | 含義 |
---|---|---|
B |
byte |
字節(jié)型數(shù) |
C |
char |
字符型數(shù) |
D |
double |
雙精度浮點(diǎn)數(shù) |
F |
float |
單精度浮點(diǎn)數(shù) |
I |
int |
整型數(shù) |
J |
long |
長(zhǎng)整數(shù) |
L<ClassName> |
reference |
ClassName 類的實(shí)例 |
S |
short |
短整數(shù) |
Z |
boolean |
布爾值 true/false
|
[ |
reference |
一個(gè)一維數(shù)組 |
特別注意:基本類型中,
long
的描述符為J
,boolean
的描述符為Z
,其他基本類型的描述符即為大寫的首字母。
舉例:
int 變量的描述符是:I
Object 類型的實(shí)例變量的描述符是:Ljava/lang/Object;
三維數(shù)組 double d[][][] 類型的變量的描述符是:[[[D
8.8.2 字段的訪問(wèn)權(quán)限和基本屬性的標(biāo)志
字段的各個(gè)訪問(wèn)標(biāo)志是可以通過(guò)位或運(yùn)算進(jìn)行組合的。
如
private static
修飾的字段就是ACC_PRIVATE
和ACC_STATIC
的組合,此時(shí)access_flags
的值就是0x0002 | 0x0008 = 0x000A
8.8.3 字段在 class
文件中的解析舉例
8.9 方法(成員方法)
包括實(shí)例初始化方法以及類或接口初始化方法在內(nèi)的所有方法(method
),都通過(guò) method_info
結(jié)構(gòu)來(lái)定義。
8.9.1 方法在 class
文件中的格式
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
其中:
access_flags:表示方法的訪問(wèn)權(quán)限和基本屬性??梢允嵌鄠€(gè)權(quán)限和屬性的標(biāo)志的組合(位或運(yùn)算)。
name_index:是對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Utf8_info,用于表示方法名。
descriptor_index:是對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Utf8_info,用于表示方法描述符。
attributes_count:表示當(dāng)前方法的附加屬性的數(shù)量。
attributes[]:表示附加屬性表,表中每個(gè)附加屬性的結(jié)構(gòu)必須是 attribute_info。
方法描述符
方法描述符(descriptor
)是一個(gè)用于 描述參數(shù)類型和返回值類型 的字符串。
無(wú)論某方法是靜態(tài)方法還是實(shí)例方法,其方法描述符都是相同的。
也就是說(shuō)無(wú)法通過(guò)一個(gè)方法的方法描述符來(lái)判斷該方法是靜態(tài)的還是非靜態(tài)的。
盡管實(shí)例方法除了傳遞自身定義的參數(shù)外,還需要額外傳遞參數(shù)
this
,但是這一點(diǎn)不是由方法描述符來(lái)表達(dá)的。參數(shù)
this
的傳遞是由Java
虛擬機(jī)中調(diào)用實(shí)例方法時(shí)所使用的字節(jié)碼指令來(lái)實(shí)現(xiàn)的。
參考 《
Java
虛擬機(jī)規(guī)范Java SE 8
版》 第4.3.3
節(jié)(方法描述符)內(nèi)容。
// 方法描述符由參數(shù)描述符列表和返回值描述符組成
MethodDescriptor:
({ParameterDescriptor})ReturnDescriptor
// 參數(shù)描述符就是 字段類型
ParameterDescriptor:
FieldType
// 返回值描述符包括: 字段類型、void 描述符
ReturnDescriptor:
FieldType
VoidDescriptor
// void 描述符就是 V,表示方法不返回任何值(即方法的返回值類型是 void)
VoidDescriptor:
V
舉例:
Object foo(int i, double d, Thread t) {...} 方法的描述符是:(IDLjava/lang/Thread;)Ljava/lang/Object;
void foo() {...} 方法的描述符是:()V
8.9.2 方法在 class
文件中的解析舉例
8.10 屬性(attribute)
屬性(attribute
)在 class
文件中的 ClassFile
結(jié)構(gòu)、field_info
結(jié)構(gòu)、method_info
結(jié)構(gòu)和 Code_attribute
結(jié)構(gòu)中都有使用。
8.10.1 屬性在 class
文件中的通用格式
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
其中:
attribute_name_index:是對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Utf8_info,用于表示屬性名。
attribute_length:給出了 info[] 數(shù)組所占的字節(jié)大小。
info[]:不同屬性的 info[] 不同,參考各個(gè)屬性的具體格式。
8.10.2 JVM
規(guī)范中預(yù)定義的屬性(23
個(gè))
8.10.3 Code
屬性
Code
屬性附加方法的附加屬性出現(xiàn)在 method_info
結(jié)構(gòu)中。
Code
屬性中包含方法(如成員方法、實(shí)例初始化方法、類或接口初始化方法)的 Java
虛擬機(jī)指令,及相關(guān)的輔助信息。
對(duì)于抽象方法(
abstract
修飾的方法)、本地方法(native
修飾的方法),方法對(duì)應(yīng)的method_info
結(jié)構(gòu)中不能有Code
屬性。除
native
、abstract
方法之外的其他方法的method_info
中則必須有,且只能有一個(gè)Code
屬性。
8.10.3.1 Code
屬性的格式
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 attributes_count;
attribute_info attributes[attributes_count];
}
其中:
attribute_name_index:是對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Utf8_info,固定為屬性名 `Code`。
attribute_length:表示從 max_stack 到 attribute_info_attributes[] 所占的字節(jié)大小。即當(dāng)前 Code 屬性在 class 文件中的剩余長(zhǎng)度。
max_stack:表示當(dāng)前方法在調(diào)用時(shí),其棧幀中的操作數(shù)棧的最大深度。
max_locals:表示當(dāng)前方法在調(diào)用時(shí),其棧幀中的局部變量表內(nèi),有多少個(gè)局部變量(包括用于傳遞參數(shù)的局部變量)。
code_length:表示 code[] 數(shù)組所占的字節(jié)大小。(code_length 的值必須大于 0 ,即 code[] 數(shù)組不能為空)
code[]:用于保存實(shí)現(xiàn)當(dāng)前方法的 `Java` 虛擬機(jī)代碼。
exception_table_length:表示 exception_table[] 數(shù)組中的元素個(gè)數(shù)。
exception_table[]:該數(shù)組中的一個(gè)元素代表 code[] 數(shù)組中的一個(gè)異常處理器。
exception_table[] 數(shù)組中的每個(gè)元素都包含如下結(jié)構(gòu):
start_pc 和 end_pc:當(dāng)前元素代表的異常處理器在 code[] 中的有效范圍是 [start_pc, end_pc]。
start_pc 的值表示對(duì) code[] 中某一指令操作碼的有效索引;
end_pc 的值要么是對(duì) code[] 中某一指令操作碼的有效索引,要么等于 code_length;
start_pc 必須小于 end_pc。
當(dāng)程序計(jì)數(shù)器在范圍 [start_pc, end_pc] 內(nèi)時(shí),當(dāng)前元素表示的異常處理器就將生效。
handler_pc:當(dāng)前元素代表的異常處理器的起點(diǎn),handler_pc 的值表示對(duì) code[] 中某一指令操作碼的有效索引。
catch_type:若值不為 0,
則表示對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Class_info,
表示當(dāng)前元素代表的異常處理器需要捕捉的異常類型。
若值為 0,
則表示當(dāng)任意異常拋出時(shí),都會(huì)調(diào)用當(dāng)前元素代表的異常處理器。
這可用于實(shí)現(xiàn) finally 語(yǔ)句。
attributes_count:表示 attributes[] 數(shù)組中的元素個(gè)數(shù)。
attributes[]:表示與 Code 屬性相關(guān)聯(lián)的其他附加屬性的集合。
該數(shù)組(屬性表)中的每個(gè)元素都必須是 attribute_info 類型的。
與 Code
屬性相關(guān)聯(lián)的附加屬性(即 attributes[]
屬性表中的屬性類型)可以是:
LineNumberTable
LocalVariableTable
LocalVariableTypeTable
StackMapTable
-
RuntimeVisibleTypeAnnotations
和RuntimeInvisisbleTypeAnnotations
8.10.3.2 LineNumberTable
該屬性用于確定 Java 源碼與 code[] 中的 `Java` 虛擬機(jī)代碼之間的對(duì)應(yīng)關(guān)系。
LineNumberTable 屬性中保存了一個(gè) line_number_table[] 數(shù)組,該數(shù)組元素的結(jié)構(gòu)為:
{
u2 start_pc;
u2 line_number;
}
通過(guò)數(shù)組元素可以表明:
Java 源文件中行號(hào)為 line_number 處的源碼,會(huì)在 code[] 數(shù)組中索引 start_pc 處的指令中發(fā)生變化。
8.10.3.3 LocalVariableTable
在方法調(diào)用時(shí),通過(guò)該屬性來(lái)確定某個(gè)局部變量的值。
LocalVariableTable 屬性中保存了一個(gè) local_variable_table[] 數(shù)組。一個(gè)數(shù)組元素表示一個(gè)局部變量。
該數(shù)組元素的結(jié)構(gòu)為:
{
u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
}
其中:
start_pc 和 length:start_pc 必須是對(duì) code[] 中某一指令操作碼的有效索引;
start_pc + length 要么是對(duì) code[] 中某一指令操作碼的有效索引,
要么是剛超過(guò) code[] 數(shù)組末尾的首個(gè)索引值。
當(dāng)程序執(zhí)行到 code[] 數(shù)組的 [start_pc, start_pc+length] 范圍內(nèi)時(shí),
該局部變量是有效的(即該局部變量必定有值存在)。
name_index:是對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Utf8_info,
表示該局部變量名。
descriptor_index:是對(duì)常量池表的一個(gè)有效索引,該索引指向的常量池項(xiàng)的類型為 CONSTANT_Utf8_info,
表示該局部變量的字段描述符(即局部變量的類型)。
index:表示在方法調(diào)用時(shí),該局部變量在棧幀的局部變量表中的索引。
如果棧幀的局部變量表中,index 索引處的局部變量是 long 或 double 類型,則占用 index 和 index+1 兩個(gè)位置。
8.10.3.4 LocalVariableTypeTable
當(dāng)局部變量的類型中包含泛型(如 T 或 List<T> 類型的局部變量),或泛型的具體類型(如 List<String> 類型的局部變量)時(shí),
這種類型的局部變量不僅會(huì)出現(xiàn)在 LocalVariableTable 屬性中,還會(huì)出現(xiàn)在 LocalVariableTypeTable 屬性中。
LocalVariableTypeTable 屬性中保存了一個(gè) local_variable_type_table[] 數(shù)組。一個(gè)數(shù)組元素表示一個(gè)局部變量。
該數(shù)組元素的結(jié)構(gòu)為:
{
u2 start_pc;
u2 length;
u2 name_index;
u2 signature_index;
u2 index;
}
與 LocalVariableTable 屬性中的 local_variable_table[] 數(shù)組元素的唯一區(qū)別在于:
local_variable_table[] 數(shù)組元素中使用 descriptor_index 指定的字段描述符來(lái)表示局部變量的類型;
local_variable_type_table[] 數(shù)組元素中使用 signature_index 指定的字段簽名來(lái)表示局部變量的類型;
注意:字段描述符 和 字段簽名 是存在區(qū)別的。
字段簽名參考 《
Java
虛擬機(jī)規(guī)范Java SE 8
版》 中第4.7.9
節(jié)內(nèi)容(Signature
屬性)
8.10.3.5 StackMapTable
該屬性用在虛擬機(jī)的類型檢查驗(yàn)證階段。
一個(gè)方法只能有 0 個(gè)或 1 個(gè) StackMapTable 屬性。
StackMapTable 屬性中保存了一個(gè) stack_map_frame[] 數(shù)組,其中 stack_map_frame 表示棧映射幀。
棧映射幀 stack_map_frame 指定了 code[] 中某一指令對(duì)應(yīng)的局部變量和操作數(shù)棧的類型。
通過(guò) StackMapTable 屬性中保存的棧映射幀集合(stack_map_frame[]),可以提高 JVM 在類型檢查的驗(yàn)證階段的效率。
8.10.3.6 RuntimeVisibleTypeAnnotations
和 RuntimeInvisisbleTypeAnnotations
RuntimeVisibleTypeAnnotations
屬性中保存了運(yùn)行時(shí)可見(jiàn)的注解集合
RuntimeVisibleTypeAnnotations
屬性中保存了運(yùn)行時(shí)不可見(jiàn)的注解集合
8.10.3.7 棧幀
8.10.3.8 局部變量表(local variable
又稱本地變量表)
8.10.3.9 Code
屬性在 class
文件中的解析舉例(Slot
可復(fù)用)
參考 《字節(jié)碼執(zhí)行引擎》 中的 局部變量表
注意:方法調(diào)用時(shí),棧幀中的局部變量表中的局部變量所占的內(nèi)存空間是可以復(fù)用的。
也就是說(shuō),LocalVariableTable
屬性中保存的局部變量在局部變量表中的索引 index
可能跟其他局部變量的索引相同。如下圖所示:
上圖中,
locals=2,即局部變量在局部變量表中總共占 2 slot 單位的內(nèi)存空間。
根據(jù) LocalVariableTable 屬性中的內(nèi)容可知,局部變量表中保存了 3 個(gè)局部變量,
其中兩個(gè)局部變量的索引是相同的(即局部變量 test 和 e 的索引相同,都為 1),
也就是說(shuō),局部變量 test 和 e 共用了 1 slot 單位的內(nèi)存空間。
9. ASM 開(kāi)發(fā)
ASM
是一個(gè) Java
字節(jié)碼操控框架。通過(guò) ASM
可以動(dòng)態(tài)生成類或者增強(qiáng)既有類的功能。
ASM
可以直接生成 class
文件,也可以在 class
文件被加載到 Java
虛擬機(jī)之前,修改 class
文件中的內(nèi)容,從而 動(dòng)態(tài)改變類行為。
class
文件中保存的二進(jìn)制數(shù)據(jù)足夠用來(lái)解析所有與Java
類相關(guān)的信息:類名稱、繼承關(guān)系、成員變量、成員方法、以及方法體中的Java
代碼。
簡(jiǎn)單的說(shuō),ASM
可以讀取并解析 class
文件的內(nèi)容,并提供接口對(duì) class
文件的內(nèi)容進(jìn)行訪問(wèn)和修改。
目前許多框架如 CGLib
、Hibernate
、Spring
都直接或間接地使用 ASM
操作字節(jié)碼。
使用
ASM
需要導(dǎo)入依賴包:asm-7.3.1.jar
和asm-util-7.3.1.jar
。
9.1 編程模型和核心 API
ASM
提供了兩種編程模型:
-
Core API
:提供了基于事件形式的編程模型。 該模型不需要一次性地將整個(gè)類的結(jié)構(gòu)讀取到內(nèi)存中。 因此這種方式更快,需要的內(nèi)存更少。 但這種編程方式難度較大。
-
Tree API
:提供了基于樹(shù)形的編程模型。 該模型需要一次性地將整個(gè)類的結(jié)構(gòu)讀取到內(nèi)存中。 因此這種方式需要的內(nèi)存更多。 但這種編程方式較為簡(jiǎn)單。
9.2 ClassVisitor 開(kāi)發(fā)
ClassVisitor
是 Core API
編程模型中提供的接口,用于對(duì)字節(jié)碼進(jìn)行操作。
ClassVisitor
接口中的每個(gè)方法對(duì)應(yīng)了class
文件中的每一項(xiàng)。
ASM
提供了三個(gè)基于 ClassVisitor
接口的實(shí)現(xiàn)類來(lái)完成 class
文件的生成和轉(zhuǎn)換。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-817708.html
-
ClassReader
:用于解析一個(gè)類的class
文件。 -
ClassAdapter
:實(shí)現(xiàn)會(huì)變化的功能。 -
ClassWriter
:用來(lái)輸出變化后的字節(jié)碼。
ASM
給我們提供了ASMifier
工具來(lái)幫助開(kāi)發(fā),可使用ASMifier
工具生成ASM
結(jié)構(gòu)來(lái)對(duì)比。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-817708.html
9.3 MethodVisitor 開(kāi)發(fā)
9.4 實(shí)現(xiàn)模擬 AOP 功能
到了這里,關(guān)于一、認(rèn)識(shí) JVM 規(guī)范(JVM 概述、字節(jié)碼指令集、Class文件解析、ASM)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!