為旅行而生
Java類文件(.class文件)是一個為已編譯Java程序仔細(xì)定義的格式。Java源代碼被編譯成能夠被任何JVM加載和執(zhí)行的類文件。在被JVM加載之前,類文件可能是由網(wǎng)絡(luò)傳輸而來。
類文件是獨立于底層平臺的,所以適用于更多的地方。它們由簡潔的JVM字節(jié)碼組成,這樣就能輕裝上陣。類文件常常被壓縮,以極快的速度通過網(wǎng)絡(luò),到達(dá)世界各地的JVM。
類文件里有什么?
Java類文件包含JVM需要知道的關(guān)于一個Java類或接口的一切。按照它們的出現(xiàn)次序,主要的部分有:魔法數(shù)(magic),版本號(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)常在長度上有變化,所以信息的實際長度在被加載之前不能被預(yù)測。例如,在方法區(qū)里的方法數(shù)目,類與類之間是不相同的,這取決于源代碼中定義的方法個數(shù)。類文件中,這些信息的實際大小或長度,被安排在信息內(nèi)容之前。這樣,當(dāng)類文件被JVM加載時,可變信息的長度首先被讀取。一旦JVM知道信息的大小,它就能正確的讀取實際的信息內(nèi)容。
類文件中,不同的相鄰信息之間通常沒有空白或填充字符;一切都以字節(jié)(byte)邊界對齊。這使得類文件很小,適合網(wǎng)絡(luò)傳輸。
為了讓JVM在加載類文件時,知道需要什么信息以及從哪里可以取得所需信息,類文件的各個組成部分的次序是嚴(yán)格定義的。例如,每個JVM都知道類文件的前8個字節(jié)由魔法數(shù)和版本號組成,常量池從第9個字節(jié)開始,訪問標(biāo)示符區(qū)緊跟在常量池后面。但是,因為常量池的長度是可變的,在讀取完常量池之前,JVM是不知道訪問標(biāo)示符區(qū)具體從什么地方開始。一旦讀取完常量池,JVM就知道接下來的2個字節(jié)就是訪問標(biāo)示符區(qū)。
魔法數(shù)(Magic)和版本號(Version)
每個類文件的開始4個字節(jié)都是0xCAFEBABE。這個神奇的數(shù)字讓Java類文件更容易識別,因為類文件以外的文件幾乎不可能也以這四個相同的字節(jié)開頭。之所以稱之為魔法數(shù),是因為它可以被文件格式設(shè)計者們從帽子里拉出來(??)。對它僅有的要求是,不能被現(xiàn)實已有的文件格式占用。根據(jù)最初Java團(tuán)隊主要成員之一的Patrick Naughton所說,遠(yuǎn)在“Java”被當(dāng)作Java語言的名稱之前,這個神奇的數(shù)字就已經(jīng)被選好了。我們當(dāng)時在尋找一個有趣,獨特并且很容易記住的數(shù)字。0xCAFEBABE作為漂亮的Peet’s Coffee的咖啡師的代稱,能預(yù)示未來Java語言的名字,這完全是一個巧合。
類文件接下來的4個字節(jié)包含了大版本號(major version)和小版本號(minor version)。這些數(shù)字標(biāo)識了特定類文件使用的類文件格式,讓JVM可以驗證類文件是否可以被載入。每個JVM都有一個它能載入的最大版本號,拒絕加載大于最大版本號的類文件。
常量池(Constant Pool)
類文件在常量池中保存與類或接口關(guān)聯(lián)的常量。常量池中能看到的部分常量是字符串字面值(literal strings),final變量的值(final variable values),類名,接口名,變量名和變量類型,方法名和方法簽名(method names and signatures)。方法簽名由方法返回值類型(return type)和一組參數(shù)類型(argument types)組成。
常量池被組織成一個元素長度可變的數(shù)組。每個常量占據(jù)數(shù)組中的一個元素。在整個類文件中,常量通過指示它們在數(shù)組中位置的整型索引來引用。第一個常量的索引值是1,第二個是2,以此類推。常量池數(shù)組的元素個數(shù)寫在常量池的前面,所以在加載類文件時,JVM知道它需要加載多少常量。
常量池中每個元素以指明自己類型的單字節(jié)標(biāo)簽(tag)開始。一旦JVM看到這個標(biāo)簽,就能知道接下來會遇到什么類型的常量。例如,如果看到一個表示字符串的標(biāo)簽,JVM會認(rèn)為接下來2個字節(jié)就是字符串的長度,然后就是“長度”個字節(jié)組成的字符串。
在本文剩下的部分,我有時會用constant_pool[n]表示常量池數(shù)組的第n個元素。從常量池組織的像個數(shù)組來說,這是有道理的;但是請記住,這些元素具有不同的大小和類型,并且第一個元素的索引是1。
訪問標(biāo)識符區(qū)(Access Flags)
常量池之后的2個字節(jié)就是訪問標(biāo)示符,它表明該文件定義的是類還是接口;該類或接口是公開的(public)還是抽象的(abstract);如果是類,該類是不是final的。
當(dāng)前類區(qū)(This class)
接下來2個字節(jié)是當(dāng)前類區(qū),它是常量池數(shù)組的索引。被當(dāng)前類引用的常量constant_pool[this_class],包含兩部分:單字節(jié)標(biāo)簽(tag)和雙字節(jié)名稱索引(name index)。標(biāo)簽等于CONSTANT_Class,一個表示本元素中包含類或接口信息的值。constant_pool[name_index]是一個包含類或接口名的字符串常量。
當(dāng)前類部分稍稍揭示了常量池是怎么被使用的。當(dāng)前類區(qū)本身只是一個常量池的索引。當(dāng)JVM查找constant_pool[this_class]時,它找到一個用標(biāo)簽表明自己是一個CONSTANT_Class得元素。JVM知道CONSTANT_Class元素在標(biāo)簽(tag)之后,總是有一個叫名稱索引(name index)的常量池雙字節(jié)索引。然后它查找constant_pool[name_index],得到包含類或接口名的字符串。
超類區(qū)(Super class)
當(dāng)前類區(qū)之后是超類區(qū),也是2個字節(jié)的常量池索引。constant_pool[super_class]是CONSTANT_Class元素,它指向當(dāng)前類所直接繼承的超類名。
接口區(qū)(Interfaces)
接口區(qū)開頭的2個字節(jié),表示文件所定義的類(或接口)實現(xiàn)的接口數(shù)目。緊接著是一個數(shù)組,它包含了類所實現(xiàn)的每一個接口在常量池中的索引。
每個接口都是常量池中的CONSTANT_Class元素,它指向接口名。
字段區(qū)(Fields)
字段部分,以表示該類或接口包含的字段數(shù)的2個字節(jié)開始。字段是一個實例變量,或者是類或接口的類變量。接下來是一個以可變長結(jié)構(gòu)為元素的數(shù)組,一個結(jié)構(gòu)一個字段。每個結(jié)構(gòu)都包含一個字段的相關(guān)信息,如字段名,字段類型,如果是final變量,還包括字段值。部分信息在結(jié)構(gòu)當(dāng)中,另一部分在常量池中由結(jié)構(gòu)所指向的位置。
這部分僅有的字段,都是由定義在該類文件中的類或接口聲明的變量;繼承自超類或接口的字段不在此列。
方法區(qū)(Methods)
方法部分,以表示類或接口中方法數(shù)目的2字節(jié)開始。這個數(shù)目,只包含當(dāng)前類顯式定義的方法,不包括繼承自超類的方法。數(shù)目之后是方法本身。
表示每個方法的結(jié)構(gòu)包含方法相關(guān)的幾條信息,包括方法描述符(method descriptor,包括返回值類型和參數(shù)列表),方法本地變量需要的棧字(stack words)數(shù),方法操作數(shù)棧(operand stack)需要的最大棧字?jǐn)?shù),方法捕獲的異常表,字節(jié)碼序列和行號表。
屬性區(qū)(Attributes)
排在最后的是屬性區(qū),它提供定義在類文件中的特定類或接口的一般信息。屬性區(qū)以2字節(jié)的屬性數(shù)目開始,然后是屬性本身。比如一個表示源碼屬性的屬性:它表示當(dāng)前類被編譯而來的源文件名。JVM會悄悄地忽略任何它們識別不了的屬性。
更多信息請查看IT技術(shù)專欄