大家好僧须,由于最近被動態(tài)加載的知識卡住巍耗,而動態(tài)加載涉及到j(luò)ava虛擬機中的加載機制亮元,因此我決定花一定的時間來學(xué)習(xí)java虛擬機,特別是類加載部分史隆,主要參照《深入理解java虛擬機》這本書進行學(xué)習(xí),這本書的pdf版請前往鏈接獲取曼验。
http://download.csdn.net/detail/hollow12384/9606072
今天是java虛擬機的第一門課程泌射,主要講解的內(nèi)容如下:
(1)java虛擬機的語言無關(guān)性
(2)Class文件結(jié)構(gòu)概述
(3)解剖Class文件每個字節(jié)的含義
java面世時就提出一個口號:”一次編寫,到處運行“鬓照,這句話表明了java的目標(biāo)是跨平臺運行熔酷,這個目標(biāo)最終怎么實現(xiàn)呢?自然是通過操作系統(tǒng)的
應(yīng)用層實現(xiàn)豺裆,而實現(xiàn)的方式就是虛擬機了纯陨。只要不同平臺的虛擬機和所有平臺都使用統(tǒng)一的程序存儲格式—–字節(jié)碼格式,就能達到跨平臺的目的留储。
這里就涉及到一個概念翼抠,什么是字節(jié)碼格式?
字節(jié)碼(英語:Bytecode)通常指的是已經(jīng)經(jīng)過編譯获讳,但與特定機器碼無關(guān)阴颖,需要直譯器轉(zhuǎn)譯后才能成為機器碼的中間代碼。字節(jié)碼通常不像源碼一樣可以讓人閱讀丐膝,而是編碼后的數(shù)值常量量愧、引用、指令等構(gòu)成的序列帅矗。
字節(jié)碼主要為了實現(xiàn)特定軟件運行和軟件環(huán)境偎肃、與硬件環(huán)境無關(guān)。字節(jié)碼的實現(xiàn)方式是通過編譯器和虛擬機器浑此。編譯器將源碼編譯成字節(jié)碼累颂,特定平臺上的虛擬機器將字節(jié)碼轉(zhuǎn)譯為可以直接執(zhí)行的指令。字節(jié)碼的典型應(yīng)用為Java bytecode凛俱。
通俗的說紊馏,字節(jié)碼就是源碼經(jīng)過編譯器后出來的東西。
字節(jié)碼本質(zhì)上就是二進制碼蒲犬。
不過朱监,java的強大之處不止在于跨平臺,java不僅具有平臺無關(guān)性原叮,而且具有很強的語言無關(guān)性赫编。什么意思呢巡蘸?
不管是什么語言,只要對應(yīng)的編譯器能夠?qū)⒊绦虼a編譯成Class文件擂送,java虛擬機并不在乎到底Class文件是由什么得來的悦荒,只要它符合Class文件結(jié)構(gòu)的要求,就能在java虛擬機中運行团甲。
前面我們已經(jīng)介紹了逾冬,java虛擬機運行的是Class文件,那么Class文件到底是什么呢躺苦?Class文件的結(jié)構(gòu)又該符合什么要求呢身腻?下面講解的就是這個內(nèi)容。
首先明確一點匹厘,Class文件是一組以8個字節(jié)為基礎(chǔ)單位的二進制流嘀趟。Class文件流中沒有分割符,這使得Class文件儲存的數(shù)據(jù)幾乎都是有效的關(guān)鍵的數(shù)據(jù)愈诚。有人可能會問她按,如果儲存的數(shù)據(jù)需要占用8個字節(jié)以上的空間怎么辦呢?這個時候就按照高位在前的方式炕柔,分割成若干個8個字節(jié)進行儲存酌泰。
那具體應(yīng)該怎么儲存呢?Class文件格式采用了類似于C語言結(jié)構(gòu)體的偽結(jié)構(gòu)進行儲存匕累。這種結(jié)構(gòu)只有兩種數(shù)據(jù)類型:無符號數(shù)和表陵刹。后面關(guān)于Class文件的結(jié)構(gòu)解析都要建立在這兩種數(shù)據(jù)類型上,因此先講解這兩種數(shù)據(jù)類型欢嘿。
無符號數(shù)屬于基本的數(shù)據(jù)類型衰琐,以u1,u2,u4,u8分別代表1個字節(jié),2個字節(jié)炼蹦,4個字節(jié)和8個字節(jié)的無符號數(shù)羡宙,無符號數(shù)可以用來描述數(shù)字,索引引用掐隐,數(shù)量值狗热,或者按照utf-8編碼構(gòu)成字符串值。
表是由多個無符號數(shù)或其他表作為數(shù)據(jù)項構(gòu)成的復(fù)合數(shù)據(jù)類型瑟枫。所有的表都習(xí)慣性的用_info結(jié)尾斗搞,因此可以用這個來區(qū)分表和無符號數(shù)。整個Class本質(zhì)上就是一張表慷妙,它由以下的數(shù)據(jù)項構(gòu)成:
無論是無符號數(shù)還是表,如果需要描述同一個類型并且數(shù)據(jù)量不多允悦,經(jīng)常會使用一個前置的容量計數(shù)器加上若干個數(shù)據(jù)項的形式膝擂,這個時候稱這一系列連續(xù)的某個類型的數(shù)據(jù)為某一類型的集合虑啤。
第二點說明了Class文件采用無符號數(shù)和表來儲存數(shù)據(jù),但是具體怎么儲存呢架馋?儲存的規(guī)則是什么呢狞山?
在正式講解之前,建議大家先下載安裝一個軟件WinHex叉寂,我們將使用這個軟件來將.class字節(jié)碼文件轉(zhuǎn)化為16進制數(shù)進行解析萍启。
接下來先對Class文件解析的規(guī)則進行解釋:
整個.class文件轉(zhuǎn)化為16進制后就是按照上述這張表進行解析。
接下來就耐心地對表里的每一項進行詳細(xì)解釋屏鳍。
在解釋之前勘纯,由于我比較傾向于用實例解釋,因此我用一個實在的java項目生成的.class文件進行解析钓瞭。
當(dāng)然驳遵,這個java項目十分簡單,只有一個類山涡,
這個類位于這個包內(nèi):
到這個包所在的目錄堤结,打開bin,在里面就可以找到.class文件(注意鸭丛,我們是對.class文件解析而不是.java文件竞穷,還記得嗎?進入java虛擬機的是.class文件而不是.java文件)
然后用winHex打開這個.class文件鳞溉,就可以看到.class文件的16進制表示了瘾带。
好了,做足了準(zhǔn)備工作穿挨,就可以開始解析了月弛。
ps:由于篇幅較長,因此我將這部分分為兩篇博客科盛,這篇博客講解的前四個屬性帽衙,剩余的屬性見JAVA虛擬機入門(1)———-類文件結(jié)構(gòu)(下)
Class文件的前4個字節(jié)稱為魔數(shù),是用于確定這個文件是否是可以被java虛擬機接受的.class文件贞绵,為啥不用后綴名來判斷呢厉萝?當(dāng)然是因為
后綴名實在是太容易改了,而文件格式制定者只要采用標(biāo)示文件格式的魔數(shù)榨崩,并且魔數(shù)沒有被其他人采用谴垫,那就可以起到標(biāo)示的作用了。Class文件的魔數(shù)是
CAFE BABE(咖啡寶貝)母蛛,是否特別好記翩剪?看我們解析出來的16機制文件前四個字節(jié),正是CAFE BABE彩郊!
魔數(shù)后面緊跟著的就是版本號了前弯,包括次版本號和主版本號蚪缀,各占2個字節(jié)。在我們的例子中就是0000和0034恕出,0000說明此版本號為0询枚,0034說明主版本號為52,因此編譯器jdk的版本就是52.0浙巫。
常量池是Class文件中出現(xiàn)的第一個表類型數(shù)據(jù)類型金蜀,而且占據(jù)著Class文件的最大空間,同時與其他項目的關(guān)聯(lián)最多的畴。因此渊抄,常量池是一個比較復(fù)雜并且重要的內(nèi)容。
大家也可能注意到苗傅,在介紹常量池的時候抒线,我用了“不定個字節(jié)”,說明常量池的字節(jié)長度是不確定的渣慕。那怎么確定常量池到底是到哪里呢嘶炭?這取決于常量池
的前兩個字節(jié)u2,在這個例子中也就是0016逊桦,這表示一共有21個常量眨猎,為什么不是22呢(0016的十進制表示就是22)?因為第一個字節(jié)是空出來
的强经,用于后面指向常量池的索引數(shù)據(jù)在特定情況下表達“不引用任何一個常量池項目”的意思睡陪,這種情況下將索引值置為0(也就是常量池第一個字節(jié))就行了。
常量池中的常量主要包括兩大類:Leteral和符號引用(Symbolic Reference)匿情。Literal類似于java描述的常量兰迫,如文本字符串,final修飾的類型炬称。符號引用主要包括三類常量:
(1)類和接口的全限定名
(2)字段的名稱和限定符
(3)方法的名稱和限定符
關(guān)于類加載的知識在下節(jié)解析汁果。
這里拋出一個問題:為什么要使用符號引用?
回到對16進制.class文件的解析玲躯,常量池中的每一個常量都是一個表据德,一共有11種表結(jié)構(gòu),他們具有共同的特征跷车,就是第一個字節(jié)(u1)表示的是這種表的類型棘利,接下來的字節(jié)根據(jù)他們各自的類型進行解析。主要參考下面的表朽缴。
還是舉我們的例子善玫,第9個字節(jié)和第10個字節(jié)(0016)表示常量池一共有21個常量,第一個常量的標(biāo)志位是07密强,根據(jù)上面的表得知為
CONSTANT_Class_Info(記得之前說過嗎蝌焚,以info結(jié)尾的一般就是表結(jié)構(gòu)了)裹唆,并且u2指定的是全限定名常量項的索引誓斥,在這個例子中
u2是0002只洒,說明全限定名常量項在第二個常量中。第二個常量的tag是01劳坑,說明是CONSTANT_Utf8_info毕谴,u2是0011,說明
utf8字符串占據(jù)的字節(jié)數(shù)是17個字節(jié)距芬,也就是從6A一直到下一行的74涝开,這17個字節(jié)代表的就是全限定名常量的名字。這里就涉及到怎么翻譯utf8縮
略編碼了框仔。
從“\u0001”到”\u007f”之間的字符用一個字節(jié)表示舀武,”\u0080”到”\u07ff”用兩個字節(jié)表示,從”\u0800”到”\uffff”用三個字節(jié)表示离斩。
在WinHex中银舱,當(dāng)你選中這17個字節(jié)時,旁邊會自動顯示相應(yīng)的圖形跛梗,在這個例子中顯示的是”javaLearning/test“寻馏,正是這個項目的完整名字!再接下去的解析也是一樣的核偿,就不多說了诚欠。
不過這里要重點說明一下,由于Class的方法漾岳,字段等都需要引用到01轰绵,也即是CONSTANT_Utf8_info,因此
CONSTANT_Utf8_info的數(shù)量直接決定了java中方法和字段的數(shù)量尼荆,CONSTANT_Utf8_info指明長度的是length字
段左腔,長度為u2,也就是16個bit耀找,共有65535種可能排序翔悠,因此方法數(shù)的瓶頸就是65535了!如果方法數(shù)超過了這個數(shù)野芒,那么將導(dǎo)致無法編譯蓄愁。
常量池21個常量過后就是訪問標(biāo)志了,在這個例子中是在Offset為000000D0這一行的76 61過后的兩個字節(jié)狞悲,也就是00 21撮抓。首先先來看訪問標(biāo)志都有什么,以及每個對應(yīng)的16進制數(shù)是什么摇锋?
注意表中的每個屬性不一定與其他屬性相斥丹拯,比如ACC_PUBLIC和ACC_FINAL站超,一個類既可以聲明為public,也可以同時聲明為final乖酬,這個時候的標(biāo)志值就是兩者的或運算了(0001 | 0010)死相。
回歸到我們的例子中,我們的例子只有聲明了public咬像,那是不是就是0001呢算撮?請注意,ACC_SUPER說明了县昂,jdk1.2以后編譯出來的
類都會有這個標(biāo)志肮柜,而根據(jù)前面我們讀到的jdk版本號,肯定大于1.2倒彰,因此絕對是帶有這個標(biāo)志的审洞。所以真正的標(biāo)志位為0001 | 0020 =
0021,和我們讀出來的結(jié)果相符合了待讳。
好了芒澜,這就是類文件結(jié)構(gòu)最基本的前面幾個字節(jié)碼了,感興趣的各位可以看JAVA虛擬機入門(1)——-類文件結(jié)構(gòu)(下)???