LetsCoding.cn

          天地之間有桿秤,拿秤砣砸老百姓。

          Java類文件的基本結(jié)構(gòu)

          歡迎來到“Under The Hood”第二期。上期我們討論了抽象計(jì)算機(jī)JVM。如果你對(duì)JVM還很陌生,你可以去看看上期的文章。本期,我們稍稍窺探一下Java類文件的基本結(jié)構(gòu)。

          為旅行而生

          Java類文件(.class文件)是一個(gè)為已編譯Java程序仔細(xì)定義的格式。Java源代碼被編譯成能夠被任何JVM加載和執(zhí)行的類文件。在被JVM加載之前,類文件可能是由網(wǎng)絡(luò)傳輸而來。

          類文件是獨(dú)立于底層平臺(tái)的,所以適用于更多的地方。它們由簡潔的JVM字節(jié)碼組成,這樣就能輕裝上陣。類文件常常被壓縮,以極快的速度通過網(wǎng)絡(luò),到達(dá)世界各地的JVM。

          類文件里有什么?

          Java類文件包含JVM需要知道的關(guān)于一個(gè)Java類或接口的一切。按照它們的出現(xiàn)次序,主要的部分有:魔法數(shù)(magic),版本號(hào)(version),常量池(constant pool),訪問標(biāo)示符區(qū)(access flags),當(dāng)前類區(qū)(this class),超類區(qū)(super class),父接口區(qū)(interfaces),字段區(qū)(fields),方法列表區(qū)(methods),屬性區(qū)(attributes)。

          保存在類文件中的信息經(jīng)常在長度上有變化,所以信息的實(shí)際長度在被加載之前不能被預(yù)測。例如,在方法區(qū)里的方法數(shù)目,類與類之間是不相同的,這取決于源代碼中定義的方法個(gè)數(shù)。類文件中,這些信息的實(shí)際大小或長度,被安排在信息內(nèi)容之前。這樣,當(dāng)類文件被JVM加載時(shí),可變信息的長度首先被讀取。一旦JVM知道信息的大小,它就能正確的讀取實(shí)際的信息內(nèi)容。

          類文件中,不同的相鄰信息之間通常沒有空白或填充字符;一切都以字節(jié)(byte)邊界對(duì)齊。這使得類文件很小,適合網(wǎng)絡(luò)傳輸。

          為了讓JVM在加載類文件時(shí),知道需要什么信息以及從哪里可以取得所需信息,類文件的各個(gè)組成部分的次序是嚴(yán)格定義的。例如,每個(gè)JVM都知道類文件的前8個(gè)字節(jié)由魔法數(shù)和版本號(hào)組成,常量池從第9個(gè)字節(jié)開始,訪問標(biāo)示符區(qū)緊跟在常量池后面。但是,因?yàn)槌A砍氐拈L度是可變的,在讀取完常量池之前,JVM是不知道訪問標(biāo)示符區(qū)具體從什么地方開始。一旦讀取完常量池,JVM就知道接下來的2個(gè)字節(jié)就是訪問標(biāo)示符區(qū)。

          魔法數(shù)(Magic)和版本號(hào)(Version)

          每個(gè)類文件的開始4個(gè)字節(jié)都是0xCAFEBABE。這個(gè)神奇的數(shù)字讓Java類文件更容易識(shí)別,因?yàn)轭愇募酝獾奈募缀醪豢赡芤惨赃@四個(gè)相同的字節(jié)開頭。之所以稱之為魔法數(shù),是因?yàn)樗梢员晃募袷皆O(shè)計(jì)者們從帽子里拉出來(??)。對(duì)它僅有的要求是,不能被現(xiàn)實(shí)已有的文件格式占用。根據(jù)最初Java團(tuán)隊(duì)主要成員之一的Patrick Naughton所說,遠(yuǎn)在“Java”被當(dāng)作Java語言的名稱之前,這個(gè)神奇的數(shù)字就已經(jīng)被選好了。我們當(dāng)時(shí)在尋找一個(gè)有趣,獨(dú)特并且很容易記住的數(shù)字。0xCAFEBABE作為漂亮的Peet’s Coffee的咖啡師的代稱,能預(yù)示未來Java語言的名字,這完全是一個(gè)巧合。

          類文件接下來的4個(gè)字節(jié)包含了大版本號(hào)(major version)和小版本號(hào)(minor version)。這些數(shù)字標(biāo)識(shí)了特定類文件使用的類文件格式,讓JVM可以驗(yàn)證類文件是否可以被載入。每個(gè)JVM都有一個(gè)它能載入的最大版本號(hào),拒絕加載大于最大版本號(hào)的類文件。

          常量池(Constant Pool)

          類文件在常量池中保存與類或接口關(guān)聯(lián)的常量。常量池中能看到的部分常量是字符串字面值(literal strings),final變量的值(final variable values),類名,接口名,變量名和變量類型,方法名和方法簽名(method names and signatures)。方法簽名由方法返回值類型(return type)和一組參數(shù)類型(argument types)組成。

          常量池被組織成一個(gè)元素長度可變的數(shù)組。每個(gè)常量占據(jù)數(shù)組中的一個(gè)元素。在整個(gè)類文件中,常量通過指示它們?cè)跀?shù)組中位置的整型索引來引用。第一個(gè)常量的索引值是1,第二個(gè)是2,以此類推。常量池?cái)?shù)組的元素個(gè)數(shù)寫在常量池的前面,所以在加載類文件時(shí),JVM知道它需要加載多少常量。

          常量池中每個(gè)元素以指明自己類型的單字節(jié)標(biāo)簽(tag)開始。一旦JVM看到這個(gè)標(biāo)簽,就能知道接下來會(huì)遇到什么類型的常量。例如,如果看到一個(gè)表示字符串的標(biāo)簽,JVM會(huì)認(rèn)為接下來2個(gè)字節(jié)就是字符串的長度,然后就是“長度”個(gè)字節(jié)組成的字符串。

          在本文剩下的部分,我有時(shí)會(huì)用constant_pool[n]表示常量池?cái)?shù)組的第n個(gè)元素。從常量池組織的像個(gè)數(shù)組來說,這是有道理的;但是請(qǐng)記住,這些元素具有不同的大小和類型,并且第一個(gè)元素的索引是1。

          訪問標(biāo)識(shí)符區(qū)(Access Flags)

          常量池之后的2個(gè)字節(jié)就是訪問標(biāo)示符,它表明該文件定義的是類還是接口;該類或接口是公開的(public)還是抽象的(abstract);如果是類,該類是不是final的。

          當(dāng)前類區(qū)(This class)

          接下來2個(gè)字節(jié)是當(dāng)前類區(qū),它是常量池?cái)?shù)組的索引。被當(dāng)前類引用的常量constant_pool[this_class],包含兩部分:單字節(jié)標(biāo)簽(tag)和雙字節(jié)名稱索引(name index)。標(biāo)簽等于CONSTANT_Class,一個(gè)表示本元素中包含類或接口信息的值。constant_pool[name_index]是一個(gè)包含類或接口名的字符串常量。

          當(dāng)前類部分稍稍揭示了常量池是怎么被使用的。當(dāng)前類區(qū)本身只是一個(gè)常量池的索引。當(dāng)JVM查找constant_pool[this_class]時(shí),它找到一個(gè)用標(biāo)簽表明自己是一個(gè)CONSTANT_Class得元素。JVM知道CONSTANT_Class元素在標(biāo)簽(tag)之后,總是有一個(gè)叫名稱索引(name index)的常量池雙字節(jié)索引。然后它查找constant_pool[name_index],得到包含類或接口名的字符串。

          超類區(qū)(Super class)

          當(dāng)前類區(qū)之后是超類區(qū),也是2個(gè)字節(jié)的常量池索引。constant_pool[super_class]是CONSTANT_Class元素,它指向當(dāng)前類所直接繼承的超類名。

          接口區(qū)(Interfaces)

          接口區(qū)開頭的2個(gè)字節(jié),表示文件所定義的類(或接口)實(shí)現(xiàn)的接口數(shù)目。緊接著是一個(gè)數(shù)組,它包含了類所實(shí)現(xiàn)的每一個(gè)接口在常量池中的索引。
          每個(gè)接口都是常量池中的CONSTANT_Class元素,它指向接口名。

          字段區(qū)(Fields)

          字段部分,以表示該類或接口包含的字段數(shù)的2個(gè)字節(jié)開始。字段是一個(gè)實(shí)例變量,或者是類或接口的類變量。接下來是一個(gè)以可變長結(jié)構(gòu)為元素的數(shù)組,一個(gè)結(jié)構(gòu)一個(gè)字段。每個(gè)結(jié)構(gòu)都包含一個(gè)字段的相關(guān)信息,如字段名,字段類型,如果是final變量,還包括字段值。部分信息在結(jié)構(gòu)當(dāng)中,另一部分在常量池中由結(jié)構(gòu)所指向的位置。

          這部分僅有的字段,都是由定義在該類文件中的類或接口聲明的變量;繼承自超類或接口的字段不在此列。

          方法區(qū)(Methods)

          方法部分,以表示類或接口中方法數(shù)目的2字節(jié)開始。這個(gè)數(shù)目,只包含當(dāng)前類顯式定義的方法,不包括繼承自超類的方法。數(shù)目之后是方法本身。

          表示每個(gè)方法的結(jié)構(gòu)包含方法相關(guān)的幾條信息,包括方法描述符(method descriptor,包括返回值類型和參數(shù)列表),方法本地變量需要的棧字(stack words)數(shù),方法操作數(shù)棧(operand stack)需要的最大棧字?jǐn)?shù),方法捕獲的異常表,字節(jié)碼序列和行號(hào)表。

          屬性區(qū)(Attributes)

          排在最后的是屬性區(qū),它提供定義在類文件中的特定類或接口的一般信息。屬性區(qū)以2字節(jié)的屬性數(shù)目開始,然后是屬性本身。比如一個(gè)表示源碼屬性的屬性:它表示當(dāng)前類被編譯而來的源文件名。JVM會(huì)悄悄地忽略任何它們識(shí)別不了的屬性。

          本文譯自:The Java class file lifestyle

          原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明: 轉(zhuǎn)載自LetsCoding.cn
          本文鏈接地址: Java類文件的基本結(jié)構(gòu)

          posted on 2014-05-19 03:49 Rolandz 閱讀(1770) 評(píng)論(2)  編輯  收藏 所屬分類: 編程實(shí)踐

          評(píng)論

          # re: Java類文件的基本結(jié)構(gòu) 2014-05-19 06:36 萬利鎖業(yè)

          謝謝博主分享  回復(fù)  更多評(píng)論   

          # re: Java類文件的基本結(jié)構(gòu) 2014-06-01 19:12 手賺網(wǎng)-手機(jī)賺錢軟件排行,手機(jī)賺錢平臺(tái),網(wǎng)絡(luò)賺錢http://www.szapk.cn

          手賺網(wǎng)-手機(jī)賺錢軟件排行,手機(jī)賺錢平臺(tái),網(wǎng)絡(luò)賺錢http://www.szapk.cn  回復(fù)  更多評(píng)論   

          導(dǎo)航

          統(tǒng)計(jì)

          留言簿(1)

          隨筆分類(12)

          隨筆檔案(19)

          積分與排名

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 彭泽县| 佳木斯市| 康马县| 蚌埠市| 阿荣旗| 岳阳县| 塘沽区| 临邑县| 清远市| 卫辉市| 明溪县| 伊川县| 樟树市| 阳信县| 武义县| 广德县| 武宁县| 广宁县| 鹿邑县| 通辽市| 乌鲁木齐市| 苍南县| 宿迁市| 隆子县| 无为县| 大化| 信宜市| 武鸣县| 内丘县| 中江县| 朝阳区| 信阳市| 精河县| 绥滨县| 洪湖市| 成都市| 多伦县| 黎城县| 阿城市| 建湖县| 同江市|