深入理解Java虛擬機(jī)之類文件結(jié)構(gòu)

什么是JVM的“無(wú)關(guān)性”?

Java具有平臺(tái)無(wú)關(guān)性困食,也就是任何操作系統(tǒng)都能運(yùn)行Java代碼边翁。之所以能實(shí)現(xiàn)這一點(diǎn),是因?yàn)镴ava運(yùn)行在Java虛擬機(jī)之上硕盹,不同的操作系統(tǒng)都擁有各自的Java虛擬機(jī)符匾,這些虛擬機(jī)都可以載入和執(zhí)行同一種平臺(tái)無(wú)關(guān)的字節(jié)碼(ByteCode),因此Java能實(shí)現(xiàn)“一次編寫瘩例,處處運(yùn)行”啊胶。
而JVM不僅具有平臺(tái)無(wú)關(guān)性芒澜,還具有語(yǔ)言無(wú)關(guān)性。平臺(tái)無(wú)關(guān)性是指任何操作系統(tǒng)都能運(yùn)行Java代碼创淡,而語(yǔ)言無(wú)關(guān)性是指Java虛擬機(jī)能運(yùn)行除Java以外的代碼痴晦!
實(shí)現(xiàn)語(yǔ)言無(wú)關(guān)性的基礎(chǔ)仍然是虛擬機(jī)和字節(jié)碼存儲(chǔ)格式。Java虛擬機(jī)不和包括Java在內(nèi)的任何語(yǔ)言綁定琳彩,它只與“Class文件”這種特定的二進(jìn)制文件格式所關(guān)聯(lián)誊酌,Class文件中包含了Java虛擬機(jī)指令集和符號(hào)表以及若干其他輔助信息÷斗Γ基于安全方面的考慮碧浊,Java虛擬機(jī)規(guī)范要求在Class文件中使用許多強(qiáng)制性的語(yǔ)法和結(jié)構(gòu)化約束,但任一門功能性語(yǔ)言都可以表示為一個(gè)能被Java虛擬機(jī)所接受的有效的Class文件瘟仿。作為一個(gè)通用的箱锐、機(jī)器無(wú)關(guān)的執(zhí)行平臺(tái),任何其他語(yǔ)言的實(shí)現(xiàn)者都可以將Java虛擬機(jī)作為語(yǔ)言的產(chǎn)品交付媒介劳较。
簡(jiǎn)而言之驹止,JVM只認(rèn)識(shí)class文件,它并不管何種語(yǔ)言生成了class文件观蜗,只要class文件符合JVM的規(guī)范就能運(yùn)行臊恋。因此目前已經(jīng)有Clojure、Groovy墓捻、JRuby抖仅、Jython、Scala等語(yǔ)言能夠在JVM上運(yùn)行砖第。它們有各自的語(yǔ)法規(guī)則撤卢,不過(guò)它們的編譯器都能將各自的源碼編譯成符合JVM規(guī)范的class文件,從而能夠借助JVM運(yùn)行它們梧兼。

語(yǔ)言無(wú)關(guān)性.png

縱觀Class文件結(jié)構(gòu)

class文件是二進(jìn)制文件放吩,它的內(nèi)容具有嚴(yán)格的規(guī)范,文件中沒有任何空格袱院,全是連續(xù)的0和1屎慢。class文件中的所有內(nèi)容被分為兩種類型:無(wú)符號(hào)數(shù)
無(wú)符號(hào)數(shù):它表示class文件中的值忽洛,這些值沒有任何類型腻惠,但有不同的長(zhǎng)度。根據(jù)這些值長(zhǎng)度的不同分為:u1欲虚、u2集灌、u4、u8,分別代表1字節(jié)的無(wú)符號(hào)數(shù)欣喧、2字節(jié)的無(wú)符號(hào)數(shù)腌零、4字節(jié)的無(wú)符號(hào)數(shù)、8字節(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ù)類型闲询,所有表都習(xí)慣性地以_info結(jié)尾。表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù)浅辙,整個(gè)Class文件本質(zhì)上就是一張表扭弧。

class文件的組織結(jié)構(gòu)

1、魔數(shù)
2记舆、本文件的版本信息
3鸽捻、常量池
4、訪問(wèn)標(biāo)志
5泽腮、類索引
6御蒲、父類索引
7、接口索引集合
8盛正、字段表集合
9删咱、方法表集合

魔數(shù)

每個(gè)Class文件的頭4個(gè)字節(jié)稱為魔數(shù)(Magic Number),它的唯一作用是確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件
魔數(shù)的作用就相當(dāng)于文件擴(kuò)展名豪筝,使用魔數(shù)而不是擴(kuò)展名來(lái)進(jìn)行識(shí)別主要是基于安全方面的考慮,因?yàn)槲募U(kuò)展名可以隨意地改動(dòng)摘能,不安全续崖,因此在class文件中標(biāo)示文件類型比較合適。
class文件的魔數(shù)是16進(jìn)制的 0xCAFEBABE (咖啡寶貝)团搞,非常具有浪漫主義色彩严望。

版本信息

緊接著魔數(shù)的4個(gè)字節(jié)是版本號(hào)。它表示該class中使用的是哪個(gè)版本的JDK逻恐。
在高版本的JVM上能夠運(yùn)行低版本的class文件像吻,但在低版本的JVM上無(wú)法運(yùn)行高版本的class文件,即使該class文件中沒有用到任何高版本JDK的特性也無(wú)法運(yùn)行复隆!

常量池

什么是常量池拨匆?

緊接著版本號(hào)之后的就是常量池。常量池中存放兩種類型的常量挽拂。
字面量:字面量比較接近于Java語(yǔ)言層面的常量概念惭每,如文本字符串、聲明為final的常量值等亏栈。
符號(hào)引用:符號(hào)引用是我們定義的各種名字台腥,包括了下面三類常量:
1宏赘、類和接口的全限定名(Fully Qualified Name)
2、字段的名稱和描述符(Descriptor)
3黎侈、方法的名稱和描述符

常量池的特點(diǎn)
  • 常量池長(zhǎng)度不固定:常量池的大小是不固定的察署,因此常量池開頭放置一個(gè)u2類型的無(wú)符號(hào)數(shù),用來(lái)存儲(chǔ)當(dāng)前常量池的容量峻汉。JVM根據(jù)這個(gè)值就知道常量池的頭尾贴汪。注:這個(gè)值是從1開始的,若為5表示池中有4個(gè)常量俱济。
  • 常量池中的常量用表來(lái)表示:常量池開頭有個(gè)常量池容量計(jì)數(shù)器嘶是,接下來(lái)就全是一個(gè)個(gè)常量了,只不過(guò)常量都是由一張張二維表構(gòu)成蛛碌,除了記錄常量的值以外聂喇,還記錄當(dāng)前常量的相關(guān)信息
  • 常量池是class文件的資源倉(cāng)庫(kù)
  • 常量池是與該class中其它部分關(guān)聯(lián)最多的部分
  • 常量池是class文件中空間占用最大的部分之一
常量池中常量的類型

常量池中的常量大體上分為:字面量符號(hào)引用蔚携。在此基礎(chǔ)上希太,根據(jù)常量的數(shù)據(jù)類型不同,又可以被細(xì)分為14種常量類型酝蜒。這14種常量類型都有各自的二維表示結(jié)構(gòu)誊辉。每種常量類型的頭1個(gè)字節(jié)都是tag,用于表示當(dāng)前常量屬于14種類型中的哪一個(gè)亡脑。

常量類型.png

常量項(xiàng)結(jié)構(gòu)總表1.png
常量項(xiàng)結(jié)構(gòu)總表2.png

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

在常量池之后是2字節(jié)的訪問(wèn)標(biāo)志堕澄。訪問(wèn)標(biāo)志是用來(lái)表示這個(gè)class文件是類還是接口、是否被public修飾霉咨、是否被abstract修飾蛙紫、是否被final修飾等。由于這些標(biāo)志都由是/否表示途戒,因此可以用0/1表示坑傅。訪問(wèn)標(biāo)志為2字節(jié),可以表示16位標(biāo)志喷斋,但JVM目前只定義了8種唁毒,未定義的直接寫0。

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

類索引星爪、父類索引浆西、接口索引集合

類索引、父類索引移必、接口索引集合是用來(lái)表示當(dāng)前class文件所表示類的名字室谚、父類名字、接口的名字。它們按照順序依次排列秒赤,類索引和父類索引各自使用一個(gè)u2類型的無(wú)符號(hào)常量猪瞬,這個(gè)常量指向CONSTANT_Class_info類型的常量,該常量的bytes字段記錄了本類入篮、父類的全限定名陈瘦。由于一個(gè)類的接口可能有好多個(gè),因此需要用一個(gè)集合來(lái)表示接口索引潮售,它在類索引和父類索引之后痊项。這個(gè)集合開頭入口的第一項(xiàng)是一個(gè)u2類型的數(shù)據(jù),即接口計(jì)數(shù)器(interfaces_count)酥诽,表示索引表的容量鞍泉。如果該類沒有實(shí)現(xiàn)任何接口,則該計(jì)數(shù)器值為0,后面接口的索引表不再占用任何字節(jié)肮帐。否則咖驮,接下來(lái)就是接口的名字索引。

字段表的集合

什么是字段表集合训枢?

字段表(field_info)用于描述接口或者類中聲明的變量托修。字段(field)包括類級(jí)變量以及實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的局部變量恒界。我們可以想一想在Java中描述一個(gè)字段可以包含什么信息睦刃?可以包括的信息有:字段的作用域(public、private十酣、protected修飾符)涩拙、是實(shí)例變量還是類變量(static修飾符)、可變性(final)耸采、并發(fā)可見性(volatile修飾符吃环,是否強(qiáng)制從主內(nèi)存讀寫)、可否被序列化(transient修飾符)洋幻、字段數(shù)據(jù)類型(基本類型、對(duì)象翅娶、數(shù)組)文留、字段名稱。上述這些信息中,各個(gè)修飾符都是布爾值,要么有某個(gè)修飾符竭沫,要么沒有燥翅,很適合使用標(biāo)志位來(lái)表示。而字段叫什么名字蜕提、字段被定義為什么數(shù)據(jù)類
型森书,這些都是無(wú)法固定的,只能引用常量池中的常量來(lái)描述。

字段表結(jié)構(gòu)的定義
字段表結(jié)構(gòu).png
  • access_flags:字段的訪問(wèn)標(biāo)志凛膏。在Java中杨名,每個(gè)成員變量都有一系列的修飾符,和上述class文件的訪問(wèn)標(biāo)志的作用一樣猖毫,只不過(guò)成員變量的訪問(wèn)標(biāo)志與類的訪問(wèn)標(biāo)志稍有區(qū)別台谍。
  • name_index:本字段名字的索引。指向一個(gè)CONSTANT_Class_info類型的常量吁断,這里面存儲(chǔ)了本字段的名字等信息趁蕊。
  • descriptor_index:描述符。用于描述本字段在Java中的數(shù)據(jù)類型等信息(下面詳細(xì)介紹)仔役。
  • attributes_count:屬性表集合的長(zhǎng)度掷伙。
  • attributes:屬性表集合。到descriptor_index為止是字段表的固定信息又兵,光有上述信息可能無(wú)法完整地描述一個(gè)字段任柜,因此用屬性表集合來(lái)存放額外的信息,比如一個(gè)字段的值(下面會(huì)詳細(xì)介紹)寒波。
    字段訪問(wèn)標(biāo)志.png

很明顯,在實(shí)際情況中,ACC_PUBLIC乘盼、ACC_PRIVATE、ACC_PROTECTED三個(gè)標(biāo)志最多只能選擇其一俄烁,ACC_FINAL绸栅、ACC_VOLATILE不能同時(shí)選擇。接口之中的字段必須有ACC_PUBLIC页屠、ACC_STATIC粹胯、ACC_FINAL標(biāo)志,這些都是由Java本身的語(yǔ)言規(guī)則所決定的辰企。

什么是描述符风纠?

成員變量(包括靜態(tài)成員變量和實(shí)例變量)和 方法都有各自的描述符。 對(duì)于字段而言牢贸,描述符用于描述字段的數(shù)據(jù)類型竹观; 對(duì)于方法而言,描述符用于描述字段的數(shù)據(jù)類型潜索、參數(shù)列表臭增、返回值。
在描述符中竹习,基本數(shù)據(jù)類型用大寫字母表示誊抛,對(duì)象類型用“L對(duì)象類型的全限定名”表示。
對(duì)于數(shù)組類型整陌,每一維度將使用一個(gè)前置的“[”字符來(lái)描述拗窃,如一個(gè)定義為“java.lang.String[][]”類型的二維數(shù)組瞎领,將被記錄為:“[[Ljava/lang/String”,一個(gè)整型數(shù)組“int[]”將被記錄為“[I”随夸。
用描述符來(lái)描述方法時(shí)九默,按照先參數(shù)列表疹鳄,后返回值的順序描述蜒谤,參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組小括號(hào)“()”之內(nèi)挤悉。如方法void inc()的描述符為“()V”背伴,方法java.lang.String toString()的描述符為“()Ljava/lang/String”叹洲,方法int
indexOf(char[]source声离,int sourceOffset楣铁,int sourceCount侈百,char[]target癌瘾,int targetOffset觅丰,int targetCount,int fromIndex)的描述符為“([CII[CIII)I”妨退。

描述符標(biāo)識(shí)字符.png

字段表集合的注意點(diǎn)

1妇萄、一個(gè)class文件的字段表集合中不能出現(xiàn)從父類/接口繼承而來(lái)字段
2、一個(gè)class文件的字段表集合中可能會(huì)出現(xiàn)沒有人為定義的字段咬荷。如編譯器會(huì)自動(dòng)地在內(nèi)部類的class文件的字段表集合中添加外部類對(duì)象的成員變量冠句,供內(nèi)部類訪問(wèn)外部類。
3幸乒、Java中只要兩個(gè)字段名字相同就無(wú)法通過(guò)編譯懦底。但在JVM規(guī)范中,允許兩個(gè)字段的名字相同但描述符不同的情況罕扎,并且認(rèn)為它們是兩個(gè)不同的字段聚唐。

方法表集合

什么是方法表集合?

Class文件存儲(chǔ)格式中對(duì)方法的描述與對(duì)字段的描述幾乎采用了完全一致的方式腔召,方法表的結(jié)構(gòu)如同字段表一樣杆查,依次包括了訪問(wèn)標(biāo)志(access_flags)、名稱索引(name_index)臀蛛、描述符索引(descriptor_index)亲桦、屬性表集合(attributes)等。

方法表結(jié)構(gòu).png

因?yàn)関olatile關(guān)鍵字和transient關(guān)鍵字不能修飾方法浊仆,所以方法表的訪問(wèn)標(biāo)志中沒有了ACC_VOLATILE標(biāo)志和ACC_TRANSIENT標(biāo)志烙肺。與之相對(duì)的,synchronized氧卧、native、strictfp和abstract關(guān)鍵字可以修飾方法氏堤,所以方法表的訪問(wèn)標(biāo)志中增加了ACC_SYNCHRONIZED沙绝、ACC_NATIVE搏明、ACC_STRICTFP和ACC_ABSTRACT標(biāo)志。

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

方法表集合的注意點(diǎn)

1闪檬、如果本class沒有重寫父類的方法星著,那么本class文件的方法表集合中是不會(huì)出現(xiàn)父類/父接口的方法表
2、本class的方法表集合可能出現(xiàn)沒有人為定義的方法 編譯器在編譯時(shí)會(huì)在class文件的方法表集合中加入類構(gòu)造器<Clinit>和實(shí)例構(gòu)造器<init>粗悯。
3虚循、重載一個(gè)方法需要有相同的簡(jiǎn)單名稱和不同的特征簽名。JVM的特征簽名和Java的特征簽名有所不同:
Java特征簽名:方法參數(shù)在常量池中的字段符號(hào)引用的集合
JVM特征簽名:方法參數(shù) + 返回值样傍。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末横缔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衫哥,更是在濱河造成了極大的恐慌茎刚,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撤逢,死亡現(xiàn)場(chǎng)離奇詭異膛锭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蚊荣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門初狰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人互例,你說(shuō)我怎么就攤上這事奢入。” “怎么了敲霍?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵俊马,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我肩杈,道長(zhǎng)柴我,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任扩然,我火速辦了婚禮艘儒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘夫偶。我一直安慰自己界睁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布兵拢。 她就那樣靜靜地躺著翻斟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪说铃。 梳的紋絲不亂的頭發(fā)上访惜,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天嘹履,我揣著相機(jī)與錄音,去河邊找鬼债热。 笑死砾嫉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窒篱。 我是一名探鬼主播焕刮,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼墙杯!你這毒婦竟也來(lái)了配并?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤霍转,失蹤者是張志新(化名)和其女友劉穎荐绝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體避消,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡低滩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了岩喷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恕沫。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖纱意,靈堂內(nèi)的尸體忽然破棺而出婶溯,到底是詐尸還是另有隱情,我是刑警寧澤偷霉,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布迄委,位于F島的核電站,受9級(jí)特大地震影響类少,放射性物質(zhì)發(fā)生泄漏叙身。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一硫狞、第九天 我趴在偏房一處隱蔽的房頂上張望信轿。 院中可真熱鬧,春花似錦残吩、人聲如沸财忽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至活尊,卻和暖如春祖凫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惠况。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工宁仔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留稠屠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓权埠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親攘蔽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容