簡(jiǎn)介
Class文件是Java虛擬機(jī)執(zhí)行引擎的數(shù)據(jù)入口琐驴,也是Java技術(shù)體系的基礎(chǔ)構(gòu)成之一绝淡。了解Class文件的結(jié)構(gòu)對(duì)后面進(jìn)一步了解虛擬機(jī)執(zhí)行引擎很有重要的意義牢酵。
我們的知道,Java是跨平臺(tái)的語言馍乙。在Java發(fā)展之初,設(shè)計(jì)者們就考慮過了在Java虛擬機(jī)上運(yùn)行其它語言的可能性撑瞧。時(shí)至今日商業(yè)機(jī)構(gòu)和開源機(jī)構(gòu)以及在Java語言之外發(fā)展出一大批在Java虛擬機(jī)上運(yùn)行的語言显蝌,如Clojure,Groovy,JRuby,Jython,Scala琅束,等等。實(shí)現(xiàn)語言無關(guān)性的基礎(chǔ)仍然是虛擬機(jī)和字節(jié)碼存儲(chǔ)格式料滥,使用Java編譯器可以把Java代碼編譯為存儲(chǔ)字節(jié)碼的Class文件葵腹,使用JRuby等其它語言的編譯器一樣可以把程序編譯成Class文件屿岂,虛擬機(jī)不需要關(guān)心Class來源于什么語言爷怀,只要它符合Class文件應(yīng)用的結(jié)構(gòu)就可以在Java虛擬機(jī)中運(yùn)行。
class類文件的結(jié)構(gòu)
Class文件是一組以8字節(jié)為基礎(chǔ)單位的二進(jìn)制流烤惊,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊地排列在Class文件之中柒室,中間沒有添加任何分隔符逗宜,這使得整個(gè)Class文件中存儲(chǔ)的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù)纺讲,沒有空隙存在。當(dāng)遇到需要占用8位字節(jié)以上空間的數(shù)據(jù)項(xiàng)時(shí)防楷,則會(huì)按照高位在前的方式分割成若干個(gè)8位字節(jié)進(jìn)行存儲(chǔ)复局。整個(gè)Class文件本質(zhì)上就是一張表。
占用大小 | 字段描述 | 數(shù)量 |
---|---|---|
4bit | magic:魔數(shù)峦剔,用于標(biāo)識(shí)文件類型角钩,對(duì)于java來說是0xCAFEBABE | 1 |
2bit | minor_version:次版本號(hào) | 1 |
2bit | major_version:主版本號(hào) | 1 |
2bit | constant_pool_count:常量池大小递礼,從1開始而不是0。當(dāng)這個(gè)值為0時(shí)辫愉,表示后面沒有常量 | 1 |
不定 | constant_pool:#常量池 | constant_pool_count-1 |
2bit | access_flags:訪問標(biāo)志恭朗,標(biāo)識(shí)這個(gè)class是類還是接口依疼、public律罢、abstract、final | 1 |
2bit | this_class:類索引 | 1 |
2bit | super_class:父類索引 | 1 |
2bit | interfaces_count:接口計(jì)數(shù)器 | 1 |
每個(gè)2bit | interfaces:接口索引集合 | interfaces_count |
2bit | fields_count:字段的數(shù)量 | 1 |
不定 | fields:#字段表 | fields_count |
2bit | methods_count:方法數(shù)量 | 1 |
不定 | methods:#方法表 | methods_count |
2bit | attributes_count:屬性數(shù)量 | 1 |
不定 | attrbutes:#屬性表 | attributes_count |
我們可以使用WinHex來查看class文件
魔數(shù)與class文件的版本
每個(gè)Class文件頭4個(gè)字節(jié)稱為魔數(shù),它的唯一作用是確定這個(gè)文件是否是一個(gè)能被虛擬機(jī)接受的Class文件稀余。
根據(jù)class示例圖可以看出魔數(shù)值(4個(gè)字節(jié))為0xCAFEBABE睛琳;緊接著第5和第6個(gè)字節(jié)是次版本號(hào)师骗,第7讨惩、8個(gè)字節(jié)是主版本號(hào)荐捻。這里需要注意高版本的JDK能向下兼容以前版本的Class文件寡夹,但不能運(yùn)行以后版本的Class文件菩掏。
常量池
常量池可以理解為Class文件之中的資源倉庫智绸。常量池中主要存放兩大類常量:字面量和符號(hào)引用访忿。字面量比較接近于Java語言層面的常量概念,如文本字符串迹恐、聲明為final的常量值等游添。而符號(hào)引用則屬于編譯原理方面的概念,包括了下面三類常量:
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
Java代碼在進(jìn)行Javac編譯的時(shí)候找都,并不像C和C++那樣有“連接”這一步驟能耻,而是在虛擬機(jī)加載Class文件的時(shí)候進(jìn)行動(dòng)態(tài)連接亡驰。也就是說凡辱,在Class文件中不會(huì)保存各個(gè)方法、字段的最終內(nèi)存布局信息洪燥,因此這些字段捧韵、方法的符合引用不經(jīng)過運(yùn)行期轉(zhuǎn)換的話無法得到真正的內(nèi)存入口地址汉操,也就無法直接被虛擬機(jī)使用。當(dāng)虛擬機(jī)運(yùn)行時(shí)芒篷,需要從常量池獲得對(duì)應(yīng)的符號(hào)引用、再在類創(chuàng)建時(shí)或運(yùn)行時(shí)解析梭伐、翻譯到具體的內(nèi)存地址之中
訪問標(biāo)志
在常量池結(jié)束之后糊识,緊接著的兩個(gè)字節(jié)代表訪問標(biāo)志(access_flags),這個(gè)標(biāo)志用于識(shí)別一些類或者接口層次的訪問信息愉耙,包括:這個(gè)Class是類還是接口:是否定義為public類型拌滋;是否定義為abstract類型;如果是類的話赌渣,是否被生命為final等
類索引坚芜、父類索引與接口索引集合
類索引和分類索引都是一個(gè)u2類型的數(shù)據(jù),而接口索引集合是一組u2類型的數(shù)據(jù)集合鸿竖,Class文件中由三項(xiàng)數(shù)據(jù)來確定這個(gè)類的繼承關(guān)系缚忧。類索引用于確定這個(gè)類的全限定名杈笔,父類索引用于確定這個(gè)類的父類的全限定名蒙具。對(duì)于接口索引集合,入口的第一項(xiàng)u2類型的數(shù)據(jù)為接口計(jì)數(shù)器店量,表示索引表的容量融师。
字段表集合
字段表用于描述接口或者類中聲明的變量旱爆。字段(field)包括類級(jí)變量以及實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的局部變量怀伦。字段表中字段的各種描述信息(作用域比如public房待,private,是否被final拜鹤,static修飾流椒,是否可序列化等)均使用標(biāo)志位表示,名稱則引用常量池中的常量來描述惯裕。
方法表集合
在方法表中蜻势,方法的描述和字段的描述基本一致域那,依次包括訪問標(biāo)志(access_flags)、名稱索引(name_index)败许、描述符索引(descriptor_index)市殷、屬性表集合(attributes)幾項(xiàng)刹衫。
方法中的代碼經(jīng)過編譯器編譯成字節(jié)碼指令后存放在方法屬性表集合中一個(gè)名為“Code”的屬性里面带迟。
如果父類方法在子類中沒有被重寫,方法表集合中就不會(huì)出現(xiàn)來自父類的方法信息嗅绰。
屬性表集合
Class文件、字段表翠语、方法表都可以攜帶自己的屬性表集合财边,以用于描述某些場(chǎng)景專有的信息酣难。為了能正確解析Class文件,在Java SE 7中預(yù)定義了21項(xiàng)屬性鲸鹦,虛擬機(jī)在運(yùn)行時(shí)會(huì)忽略他不認(rèn)識(shí)的屬性。