《深入理解java虛擬機(jī)》- 04 類文件結(jié)構(gòu)

1、Class類文件的結(jié)構(gòu)

class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊的排列在class文件中,中間沒(méi)有添加任何分隔符。當(dāng)遇到需要占用8位字節(jié)以上空間的數(shù)據(jù)項(xiàng)時(shí),則按照高位在前的方式分割成若干個(gè)8位字節(jié)存儲(chǔ)。

class文件格式只有兩種數(shù)據(jù)類型:無(wú)符號(hào)數(shù)和表。

無(wú)符號(hào)數(shù)屬于基本數(shù)據(jù)類型,以u(píng)1、u2、u4、u8分別代表1個(gè)字節(jié)、2個(gè)字節(jié)、4個(gè)字節(jié)、8個(gè)字節(jié)的無(wú)符號(hào)數(shù),無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字、索引引用、數(shù)量值或按照UTF-8編碼構(gòu)成的字符串值。

表是由多個(gè)無(wú)符號(hào)數(shù)或其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型,以"_info"結(jié)尾,整個(gè)class文件就是一張表:

Class文件格式

1.1 魔數(shù)與Class文件版本

(1) Class文件的魔數(shù)值是:0XCAFEBABE(音譯:咖啡寶貝)

(2) 緊接著魔數(shù)的4個(gè)字節(jié)是版本號(hào),第5和第6個(gè)字節(jié)是次版本號(hào)(Minor Version),第7和第8是主版本號(hào)(Major Version)

1.2 常量池

(1) 版本號(hào)之后是常量池入口,常量池是Class文件中的資源倉(cāng)庫(kù)

(2) 常量池入口放置的是constant_pool_count,計(jì)數(shù)從1開(kāi)始,第0項(xiàng)常量表示“不引用任何一個(gè)常量池項(xiàng)目”

(3) 常量池存放兩大類常量:字面量(Literal)和符號(hào)引用(Symbolic Reference),字面量包括文本字符串、聲明為final的常量值等,而符號(hào)引用包括3類:類和接口的全限定名、字段的名稱和描述符,方法的名稱和描述符,共有14種結(jié)構(gòu)不同的表數(shù)據(jù):

常量池的項(xiàng)目類型

(4) 專門用于分析Class文件字節(jié)碼的工具:javap

1.3 訪問(wèn)標(biāo)志

在常量池之后,緊接著的兩個(gè)字節(jié)是訪問(wèn)標(biāo)志(access_flags),用于標(biāo)識(shí)類或接口的訪問(wèn)信息,含義見(jiàn)下表:

訪問(wèn)標(biāo)志

1.4 類索引、父類索引和接口索引

類索引(this_class)和父類索引(super_class)都是一個(gè)u2類型的數(shù)據(jù),而接口索引集合(interfaces)是一組u2類型數(shù)據(jù)的集合,Class文件中由這三項(xiàng)數(shù)據(jù)來(lái)確定這個(gè)類的繼承關(guān)系。查找過(guò)程:常量池索引值 -- CONSTANT_Class_info --?CONSTANT_Utf8_info :

1.5 字段表集合

字段表(field_info)用于描述接口或類中聲明的變量,包括類級(jí)變量(含static關(guān)鍵字)和實(shí)例級(jí)字段,不包括方法類的局部變量。字段表結(jié)構(gòu)如下:

字段表結(jié)構(gòu)

access_flags如下圖:

name_index和descriptor_index都是對(duì)常量池的引用,分別代表字段的簡(jiǎn)單名稱和字段的描述符。描述符用來(lái)描述字段的數(shù)據(jù)類型,標(biāo)識(shí)字符含義如下:

描述符標(biāo)識(shí)字符含義

對(duì)于數(shù)組類型,每一維度使用前置"["表示,如String[][]將被記錄為[[Ljava/lang/String,int[]表示[I

1.6 方法表集合

方法表含義

其中name_index(簡(jiǎn)單名稱)代表沒(méi)有類型和參數(shù)修飾的方法,descriptor_index(描述符)用來(lái)描述方法的參數(shù)列表(包括數(shù)量、類型和順序)和返回值

方法訪問(wèn)標(biāo)志

方法中的代碼存放在"Code"屬性表里,編譯器自動(dòng)添加的方法:類構(gòu)造器<clinit>方法和實(shí)例構(gòu)造器<init>方法

1.7 屬性表集合

詳見(jiàn):https://www.cnblogs.com/flyingcr/p/10428285.html

2、字節(jié)碼指令簡(jiǎn)介

字節(jié)碼指令由一個(gè)字節(jié)長(zhǎng)度,代表著某種特定操作含義的數(shù)字(操作碼,Opcode),以及其后0至多個(gè)此操作所需參數(shù)(操作數(shù),Operands)構(gòu)成

2.1 字節(jié)碼與數(shù)據(jù)類型

大多數(shù)的指令包含了其操作所對(duì)應(yīng)的數(shù)據(jù)類型信息,如iload指令用于從局部變量表加載int型數(shù)據(jù)到操作數(shù)棧,而fload用于加載float型數(shù)據(jù)。助記符與數(shù)據(jù)類型對(duì)代應(yīng)關(guān)系:i代表int,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。

2.2 加載和存儲(chǔ)指令

加載和存儲(chǔ)指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來(lái)回傳輸。load代表將局部變量加載到操作數(shù)棧,store將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表,push和const代表將常量加載到操作數(shù)棧

2.3 運(yùn)算指令

加法指令:iadd?ladd?fadd?dadd

減法指令:isub lsub fsub dsub

乘法指令:imul lmul fmul dmul

除法指令:idiv ldiv fdiv ddiv

求余指令:irem lrem frem drem

取反指令:ineg lneg fneg dneg

位移指令:ishl ishr iushrl shl lshr lushr

按位或指令:ior lor

按位與指令:iand land

按位異或指令:ixor lxor

局部變量自增指令:iinc

比較指令:dcmpg dcmpl fcmpg fcmpl lcmp

2.4 類型轉(zhuǎn)化指令

窄化類型轉(zhuǎn)化時(shí),需顯示的使用轉(zhuǎn)化指令:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f

2.5 對(duì)象創(chuàng)建和訪問(wèn)指令

創(chuàng)建類實(shí)例的指令:new

創(chuàng)建數(shù)組的指令:newarray(基本類型數(shù)組)、anewarray(引用類型數(shù)組)、multianewarray(多維度引用類型數(shù)組)

訪問(wèn)類字段的指令:getstatic、putstatic

訪問(wèn)實(shí)例字段的指令:getfield、putfield

將數(shù)組加載到操作數(shù)棧的指令:aload

將操作數(shù)棧的值存儲(chǔ)到數(shù)組元素的指令:astore

取數(shù)組元素長(zhǎng)度的指令:arraylength

檢查實(shí)例類型的指令:instanceof、checkcast

2.6 操作數(shù)棧管理指令

pop:棧頂一個(gè)元素出棧,pop2:棧頂兩個(gè)元素出棧

dup:復(fù)制棧頂一個(gè)數(shù)值并將復(fù)制值重新壓入棧頂,dup2:復(fù)制棧頂兩個(gè)數(shù)值并將雙份復(fù)制值重新壓入棧頂

swap:將棧頂?shù)膬蓚€(gè)數(shù)值互換

2.7 控制轉(zhuǎn)移指令

分支條件
switch分支
無(wú)條件分支

2.8 方法調(diào)用和返回指令

invokevirtual:用于調(diào)用對(duì)象的實(shí)例方法,根據(jù)對(duì)象的實(shí)際類型進(jìn)行分派

invokeinterface:用于調(diào)用接口方法,找到實(shí)現(xiàn)該接口合適的類的方法

invokespecial:用于調(diào)用實(shí)例初始化方法、私有方法和父類方法

invokestatic:用于調(diào)用static方法

invokedynamic:用于在運(yùn)行時(shí)動(dòng)態(tài)解析出調(diào)用點(diǎn)限定符所引用的方法,并執(zhí)行該方法

2.9 異常處理指令

顯示拋出異常的操作(throw語(yǔ)句)都由athrow指令實(shí)現(xiàn)

2.10 同步指令

Java虛擬機(jī)支持方法級(jí)和方法內(nèi)部一段指令序列的同步,這兩種同步結(jié)構(gòu)都是使用管程(Monitor)來(lái)支持的。

方法級(jí)的同步是隱式,則無(wú)需通過(guò)字節(jié)碼指令來(lái)控制,它實(shí)現(xiàn)在方法調(diào)用和返回操作之中。虛擬機(jī)可以從方法常量池的方法表結(jié)構(gòu)中的ACC_SYNCHRONIZED訪問(wèn)標(biāo)志區(qū)分一個(gè)方法是否是同步方法。當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的ACC_SYNCHRONIZED訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程先持有管程,然后再執(zhí)行方法,最后在方法完成(無(wú)論是正常完成還是非正常完成)時(shí)釋放管程。在方法執(zhí)行期間,執(zhí)行線程持有了管程,其他線程都無(wú)法再獲得同一個(gè)管程。如果一個(gè)同步方法執(zhí)行期間拋出了異常,并且在方法內(nèi)部無(wú)法處理異常,那這個(gè)同步方法所持有的管程將在異常拋到同步方法之外時(shí)自動(dòng)釋放。

同步指令由synchronized塊來(lái)表示的,Java虛擬機(jī)的指令集中有monitorenter和monitorexit兩條指令來(lái)支持synchronized關(guān)鍵字的語(yǔ)義,正確實(shí)現(xiàn)synchronized關(guān)鍵字需要編譯器與Java虛擬機(jī)兩者協(xié)作支持。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容