什么是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)行它們梧兼。
縱觀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è)亡脑。
訪問(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。
類索引星爪、父類索引浆西、接口索引集合
類索引、父類索引移必、接口索引集合是用來(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)的定義
- 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ì)介紹)寒波。
很明顯,在實(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”妨退。
字段表集合的注意點(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)等。
因?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)志。
方法表集合的注意點(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ù) + 返回值样傍。