详解Class类文件的结构(下)
本文繼續(xù)使用上次的Test.class文件,它是由下面單獨(dú)的一個(gè)類文件編譯而成的,沒有包。
6. 索引(Index)
索引又分類索引、父類索引和接口索引集合,類索引(this_class)和父類索引(super_class)都是一個(gè)u2類型的數(shù)據(jù),而接口索引集合(interfaces)是一組u2類型的數(shù)據(jù)的集合,Class文件依靠這些索引數(shù)據(jù)來確定這個(gè)類的繼承關(guān)系。所有類(除了java.lang.Object)都只有一個(gè)父類索引(Java的單繼承),即父類索引不為0,只有java.lang.Object的父類索引為0。接口索引用來描述該類實(shí)現(xiàn)了哪些接口,它們的出現(xiàn)順序是按照implements語句后接口的先后順序出現(xiàn)的,如果這個(gè)類是一個(gè)接口就按照extends后面出現(xiàn)的順序來。
類索引和父類索引各自指向一個(gè)CONSTANT_Class_info的類描述符常量,然后通過CONSTANT_Class_info可以定位到一個(gè)CONSTANT_Utf8_info類型的常量中的全限名字符串。而接口索引集合則以接口計(jì)數(shù)器開頭,和前面常量池類似,若計(jì)數(shù)器表示n則后面緊跟著的n個(gè)u2數(shù)據(jù)是表示該類實(shí)現(xiàn)的n個(gè)接口的類索引,分別指向?qū)?yīng)的類描述符常量。
全限名:"java/lang/Object"表示Object類的全限名,將類全名中的“.”替換成“/”而已,多個(gè)全限名之間是“;”分隔。
仍然以我上次的那個(gè)Test.class文件為例,這里三個(gè)u2類型的值分別為Ox0005、Ox0006、Ox0000,前兩個(gè)分別表示的是類索引、父類索引所指向的常量描述符。第三個(gè)表示接口集合的個(gè)數(shù),這里為0即沒有實(shí)現(xiàn)任何接口。假設(shè)為2,則表示接下來的2個(gè)u2數(shù)據(jù)表示實(shí)現(xiàn)的兩個(gè)接口,每個(gè)u2數(shù)據(jù)也指向的是常量描述符。
7.字段表集合(Field Info)
字段表(field_info)用于描述接口或者類中聲明的變量。字段包括類級(jí)變量以及實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的局部變量。字段包含的信息比較多,包含以下內(nèi)容:
- 字段的作用域:public、private、protect修飾符
- 變量類型(類變量or實(shí)例變量):static
- 可變性:final
- 并發(fā)可見性:volatile
- 可否序列化:transient
- 數(shù)據(jù)類型:基本數(shù)據(jù)類型、對(duì)象、數(shù)組
- 字段名稱
上面的這些信息除了字段數(shù)據(jù)類型和字段名稱其他都是以布爾值來描述的,有就是true且對(duì)應(yīng)一個(gè)標(biāo)志位,沒有則false,這種表示方法和上一節(jié)的Access Flags一樣。字段數(shù)據(jù)類型和字段名稱是引用的常量池中的常量來描述,可能是CONSTANT_Class_info也可能是CONSTANT_Utf8_info。
根據(jù)Java語言的語法我們可以知道,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三個(gè)標(biāo)志只能選一個(gè),ACC_FINAL、ACC_VOLATILE不能同時(shí)存在,接口必須有ACC_PUBLIC、ACC_STATIC、ACC_FINAL標(biāo)志。
描述符
描述符的作用是用來描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(數(shù)量、類型、順序)和返回值。其中基本數(shù)據(jù)類型以及void返回值類型都是用一個(gè)大寫字母來表示的,對(duì)象的類型由一個(gè)L加對(duì)象全限名表示。
基本數(shù)據(jù)類型和普通類型都已經(jīng)知道怎么表示了,但Java中有一個(gè)特殊類型就是數(shù)組類型,它是在編譯期產(chǎn)生的,它的描述符是在變量描述符前面加一個(gè)"[",如果是二維則加兩個(gè)[,比如"[["。例如一個(gè)String[][]記錄為[[Ljava/lang/String,一個(gè)int[]記錄為[I。
如果是描述一個(gè)方法則在描述符前面加一個(gè)括號(hào)“()”,如果有參數(shù)則在其中按順序添加描述符即可。例如一個(gè)String toString(char[] c,int a,String[] b)的描述符為:“([CI[Ljava.lang.String)Ljava.lang.String”。
這里同樣以Test.class文件來驗(yàn)證,第一個(gè)u2數(shù)據(jù)是容量技術(shù)器fields_count,這里是Ox0000,說明沒有字段表數(shù)據(jù),看文章開頭的java代碼,確實(shí)沒有定義任何字段。由于在編譯class文件開始沒有考慮周全,沒有定義字段,這里容量技術(shù)器為0也就看不到后面的字段描述內(nèi)容,這里先假設(shè)是Ox0001,即有一個(gè)字段。第二個(gè)u2數(shù)據(jù)是訪問標(biāo)識(shí)符access_flags,假設(shè)這里是Ox0002,說明字段標(biāo)志為ACC_PRIVATE。第三個(gè)u2數(shù)據(jù)是字段名稱name_index,假設(shè)值為Ox0005,指向#5的常量池CONSTANT_Utf8_info字符串。第四個(gè)u2數(shù)據(jù)是字段描述符,這里是Ox0007,指向#7的常量池字符串。
8. 方法表集合
方法表的描述和字段表集合描述形式一樣,只需要按照對(duì)應(yīng)的表格對(duì)照就可以了。方法表結(jié)構(gòu)依次包含了access_flags(訪問標(biāo)志)、name_index(方法名索引)、descriptor_index(描述符索引)、attribute(屬性表集合)幾項(xiàng)。方法內(nèi)的具體代碼存放在屬性表集合attribute的名為“Code”的屬性里面。
方法表結(jié)構(gòu)表:
方法訪問標(biāo)志表:
繼續(xù)以Test.class文件分析,容量計(jì)數(shù)器methods_count的值為Ox0002,表示由兩個(gè)方法,疑惑?看文章開頭的代碼只有一個(gè)main方法啊,為什么會(huì)有兩個(gè)?其實(shí)字節(jié)碼中包含了平時(shí)省略了的無參構(gòu)造方法<init>。
緊跟著的是2個(gè)方法描述集合,這里以第一個(gè)無參構(gòu)造來解釋,首先是訪問標(biāo)志access_flags,值是Ox0001,查表可知是ACC_PUBLIC類型的,然后是方法名索引name_index,值是Ox0007,指向的是常量池CONSTANT_Utf8_info字符串,即#7,我們查看反編譯的代碼可以看到#7確實(shí)是<init>。
然后是描述符索引descriptor_index,值是Ox0008指向的是常量項(xiàng)#8,反編譯后看到是()V,構(gòu)造方法無返回值,所以用的void的標(biāo)識(shí)字符V,但是在書寫代碼時(shí)不能顯式加void,因?yàn)槠潋?yàn)證是在編譯期。緊接著的是屬性表集合的屬性計(jì)數(shù)量attributes_count,這里是Ox0001,說明只有一個(gè)屬性,即前面說的“Code”屬性。
接下來的就是分別表示每一個(gè)屬性的具體指向,這里只有一個(gè)當(dāng)然就只需看一個(gè)u2數(shù)據(jù),這里是Ox0009,指向的是常量項(xiàng)#9,反編譯結(jié)果#9確實(shí)是Code。
如果方法在子類中沒有被重寫,方法表集合中就不會(huì)出現(xiàn)來自父類的信息。
從方法表集合可以看出,Class文件對(duì)一個(gè)方法的特征識(shí)別(《Java虛擬機(jī)規(guī)范》稱之為特征簽名)有很多,比如方法描述符、訪問控制標(biāo)志、返回值、屬性表等。
這里我想起來了之前騰訊一個(gè)面試官問我的問題“重載的驗(yàn)證是在哪個(gè)階段?”,當(dāng)時(shí)我沒回答好這個(gè)問題,看了《深入理解Java虛擬機(jī)》這一節(jié)的內(nèi)容才知道,對(duì)于Java方法的重載是在編譯器驗(yàn)證的,在Java語義里規(guī)定:只要方法名、參數(shù)內(nèi)容及順序相同則視為非法重載,而對(duì)返回值、修飾符等沒有嚴(yán)格要求。而在Class文件里對(duì)一個(gè)方法的特征簽名比編譯期的多,也就是說如果兩個(gè)方法有相同的名稱和特征簽名,但返回值不同,那么也是可以合法存在于同一個(gè)Class文件的。
9.屬性表集合
屬性表(attribute_info)存在于Class文件、字段表、方法表等,它用于描述某些場(chǎng)合專有的信息。在class文件中對(duì)屬性表的限定并不是很嚴(yán)格,只要不要與已有屬性名重復(fù),任何不人實(shí)現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱约憾x的屬性信息,虛擬機(jī)在運(yùn)行時(shí)會(huì)忽略掉它不認(rèn)識(shí)的屬性。這一部分內(nèi)容較多并且不固定,建議讀者閱讀最新的《Java虛擬機(jī)規(guī)范》或《深入理解Java虛擬機(jī)——周志明 著》。
本文是筆者閱讀《深入理解Java虛擬機(jī)》一書時(shí)的簡(jiǎn)單總結(jié)和實(shí)踐。參考文獻(xiàn):《Java虛擬機(jī)規(guī)范(第二版)》、《深入理解Java虛擬機(jī)》
END
總結(jié)
以上是生活随笔為你收集整理的详解Class类文件的结构(下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 12岁男孩经常熬夜玩手机诱发癫痫!专家提
- 下一篇: 依赖注入和控制反转的理解,写的太好了。