原文鏈接:http://blog.csdn.net/hollow12384/article/details/52189590
轉(zhuǎn)載請(qǐng)注明出處
大家好华弓,這篇博客是繼JAVA虛擬機(jī)入門(1)—–類文件結(jié)構(gòu)(上)的冯勉,沒看過那篇博客的最好先看那篇博客~(鏈接:http://blog.csdn.net/hollow12384/article/details/52183804)
由于始終圍繞上篇博客的例子進(jìn)行講解戈盈,這次還是先把圖放出來初澎,這圖和上篇博客的一模一樣制恍,只是為了大家方便觀看墨林。
(五)類索引饥伊、父類索引,接口類索引集合(至少4個(gè)字節(jié))
承接在訪問標(biāo)志后的就是類索引荷鼠、父類索引句携,接口類索引集合。類索引用于確定這個(gè)類的全限定名允乐,有2個(gè)字節(jié)矮嫉;父類索引用于確定這個(gè)類的父類的全限定名,有2個(gè)字節(jié)牍疏,且除了Object類外蠢笋,其余類這個(gè)索引不可能為空(都默認(rèn)繼承Object);接口類索引集合跟之前的常量池接口差不多鳞陨,前兩個(gè)字節(jié)表示一共有多少個(gè)接口昨寞,如果是00 00說明沒有implement 接口。
還是針對(duì)上篇博客中的例子厦滤,跟在0021后面的就是 00 01援岩,說明這個(gè)類的全限定名要去第一個(gè)常量找,找到常量池中的第一個(gè)常量掏导,為07 00 02享怀,上次我們解析過了,07代表的是CONSTANT_Class_info碘菜,是一個(gè)指向類的索引凹蜈,這里0002指向的是第二個(gè)常量限寞,由于之前已經(jīng)解析過了,這里就不再贅述仰坦,第二個(gè)常量解析出來的結(jié)果正是javaLearning/test履植,也就是這個(gè)類的全限定名。
緊跟著00 01的是00 03悄晃,說明父類權(quán)限定名要去第三個(gè)常量找玫霎,步驟和上面的一模一樣,解析出來的結(jié)果是Java/lang/Object妈橄,沒錯(cuò)吧庶近!沒有繼承其他類的類默認(rèn)繼承Object。接下來就是00 03后面的00 00眷蚓,我們說過鼻种,00 00代表的是沒有接口,在我們這個(gè)例子中確實(shí)沒有繼承接口沙热,bingo叉钥。
(六)字段表
回顧一下,到目前為止篙贸,我們已經(jīng)解析了魔數(shù)投队,版本號(hào),常量池爵川,訪問標(biāo)志和類索引敷鸦、父類索引和接口索引集合,接下來將正式進(jìn)入類里面探索每一個(gè)字段在字節(jié)碼文件中怎么表示的寝贡。在索引集合后跟著的就是字段表了扒披。什么是字段呢?舉個(gè)例子吧兔甘,private int k = 0谎碍,這里的k就可以理解為是字段鳞滨。當(dāng)然洞焙,字段實(shí)際上是包括它的屬性的,比如private和int等拯啦。首先我們先看一下字段表的結(jié)構(gòu)澡匪,這會(huì)指引我們對(duì)字節(jié)碼進(jìn)行解析:
要注意表中的數(shù)據(jù)是針對(duì)于一個(gè)field(字段)的,但是我們知道褒链,在一個(gè)類中唁情,往往有許多字段,比如private int a; private int b; 這樣的話就需要一個(gè)計(jì)數(shù)器來表示一共有多少個(gè)字段了甫匹,因此字段表一開始的2個(gè)字節(jié)就是用于表示一共有多少個(gè)字段的甸鸟。在我們的例子中是00 01惦费,也就是說只有一個(gè)字段,這是當(dāng)然抢韭。因?yàn)槲覀兙椭挥幸粋€(gè)字段:private int a = 1;
接下來薪贫,我們將結(jié)合實(shí)例對(duì)里面每一個(gè)字段進(jìn)行解釋。
(1)access_flags(2個(gè)字節(jié))
還記得(四)中的訪問標(biāo)志嗎刻恭,這里的access_flags和那個(gè)差不多瞧省。下面是access_flags可以設(shè)置的一些屬性。
不過注意里面的這些屬性鳍贾,有些是可以在一起設(shè)置的鞍匾,有些是相斥的,具體哪些相斥可以參考java制定規(guī)則骑科,這里由于將焦點(diǎn)放在解析字節(jié)碼文件橡淑,因此不多贅述。在我們的例子中咆爽,access_flags是在00 01后的00 02梳码,根據(jù)上面的屬性表可以得知對(duì)應(yīng)的是ACC_PRIVATE,正確伍掀。
(2)name_index(2個(gè)字節(jié))
name_index表示的是字段的簡單名稱的索引掰茶,何謂簡單名稱呢?這是相對(duì)于全限定名而言的蜜笤,在(五)中我們已經(jīng)知道濒蒋,全限定名就是類似于javaLearning/test?這種結(jié)構(gòu)的(包/類),而簡單名稱則就是一個(gè)名字而已把兔,比如private int a = 1(還是這個(gè)例子)沪伙,簡單名稱就是a。知道這個(gè)概念后县好,看回例子围橡,name_index為00 05,去常量池的第5個(gè)常量找缕贡,對(duì)應(yīng)的位置是Offset為00000030那一行的01 0001 61翁授,這個(gè)就是第五個(gè)常量了,01表示是CONSTANT_Utf8_Info晾咪,0001說明長度為1個(gè)字節(jié)收擦,61表示的是‘a(chǎn)’,怎樣谍倦?是不是我們例子中的那個(gè)字段a塞赂!再一次成功解出。
(3)descriptor_index(2個(gè)字節(jié))
我們已經(jīng)解析出來private和a昼蛀,但是還有一個(gè)關(guān)鍵的a的類型還沒解析出來宴猾,那應(yīng)該怎么得到a的類型int呢圆存?這就需要描述符了。描述符是一個(gè)相對(duì)于前面兩個(gè)復(fù)雜一點(diǎn)的東西仇哆。它主要是用于描述字段的數(shù)據(jù)類型辽剧、方法的參數(shù)列表(包括數(shù)量、類型和順序)和返回值税产。首先我們先講解描述符的描述規(guī)則怕轿,一旦理解這個(gè),解析也就so easy了辟拷。根據(jù)描述符的描述規(guī)則撞羽,基本數(shù)據(jù)類型(int,byte衫冻,char诀紊,double,float隅俘,long邻奠,short,boolean)以及代表無返回值的void都用一個(gè)大寫字符表示为居,而對(duì)象類型就用L + 對(duì)象全限定名表示碌宴,而數(shù)組類型每一個(gè)維度都用一個(gè)前置的”[“表示。舉個(gè)例子:String[][][]表示為[[[Ljava.lang.String蒙畴,int[]表示為[I具體如下表:
大家也注意到了贰镣,在說描述符時(shí),我并沒有局限于描述字段膳凝,而是提到了方法碑隆,沒錯(cuò),描述符也用于描述方法蹬音。用描述符描述方法時(shí)上煤,按照先參數(shù)列表再返回值的順序描述。參數(shù)列表按照參數(shù)的嚴(yán)格順序放置在()中著淆。繼續(xù)舉個(gè)例子:int getIntTest()用描述符描述就是()I劫狠,void k(int a,String[]k,int b)用描述符描述就是(I[Ljava.lang.StringI)V。相信大家對(duì)描述符怎么得來的應(yīng)該已經(jīng)有了一定理解牧抽,接下來就是實(shí)戰(zhàn)了嘉熊。再次獻(xiàn)上那個(gè)遠(yuǎn)古的例子(參照本博客剛開頭那張表)遥赚,在實(shí)例中descriptor_index對(duì)應(yīng)的字節(jié)碼是00 06扬舒,去常量池中第六個(gè)常量中找,位置是offset為00000030的01 00 01 49凫佛,對(duì)應(yīng)的值為I讲坎,也就回答了我們?cè)谇懊嫣岬降膯栴}了孕惜,這個(gè)I就是int。在descriptor_index后跟著的是attrubute_count晨炕,但是因?yàn)樵谶@里是00 00衫画,說明沒有其他屬性了。不過如果是final static int a = 9瓮栗;那么屬性表集合將會(huì)有一個(gè)指向常量池中9的屬性削罩。
(七)方法表集合
在字段表后面就是方法表集合了,由于方法表集合和字段表集合十分類似费奸,因此直接給出方法表結(jié)構(gòu)以及方法表訪問結(jié)構(gòu)方法表結(jié)構(gòu)如下:
[圖片上傳中弥激。。愿阐。(5)]
方法訪問標(biāo)志如下:
接著就直接對(duì)實(shí)例中的方法表進(jìn)行解析了(畢竟真的和字段表很像):
看到Offset為000000E0中的00 02微服,說明方法表一共有兩個(gè)方法。咦缨历?我們不是只寫了一個(gè)方法嗎以蕴?怎么這里說是兩個(gè)?其實(shí)辛孵,要注意到丛肮,每個(gè)類都會(huì)有屬于自己的構(gòu)造函數(shù),如果自己沒有寫構(gòu)造函數(shù)的話魄缚,那么編譯器會(huì)幫我們添加一個(gè)實(shí)例構(gòu)造器腾供,因此這多出來的一個(gè)方法就是構(gòu)造方法了。
接下來看第一個(gè)方法鲜滩,00 01說明是ACC_PUBLIC伴鳖,00 07找到常量池的第七個(gè)常量,位于Offset為00000030的01 00 06 3C 69 6E 69 74 3E徙硅,翻譯出來就是榜聂,再看00 08指向常量池的第8個(gè)常量,讀出來為()V嗓蘑,說明是返回值為0须肆,參數(shù)為空的函數(shù);接下來的00 01說明屬性表只有一個(gè)屬性桩皿,是00 09豌汇,讀取第9個(gè)常量,讀出來是Code泄隔,說明這個(gè)屬性是方法的字節(jié)碼描述拒贱。總結(jié)一下就是我們讀出來一個(gè)public的無參構(gòu)造函數(shù),而關(guān)于方法的字節(jié)碼描述在后面的屬性表中會(huì)進(jìn)行解釋逻澳。
第二個(gè)方法是00 01 —-> public闸天,00 09,找到第九個(gè)常量斜做,0A 00 03 00 0B苞氮,關(guān)于常量池中的tag在上篇博客有給出一張表,根據(jù)里面的規(guī)則進(jìn)行轉(zhuǎn)化瓤逼,最后的結(jié)果是getIntTest笼吟,就是方法的名字了。
注意:
(1)一個(gè)類如果沒有override一個(gè)父類的方法霸旗,那么父類的方法不會(huì)出 現(xiàn)在這個(gè)類的常量池中
(2)在java中重載一個(gè)方法赞厕,其實(shí)除了一個(gè)和原方法一模一樣的名稱外,還需要一個(gè)與原方法不同的特征簽名定硝。什么是特征簽名呢皿桑?特征簽名是一個(gè)方法中各個(gè)參數(shù)在常量池中的字段引用的集合,而返回值是不屬于方法中參數(shù)的蔬啡,因此無法通過返回值來對(duì)一個(gè)原有的方法進(jìn)行重載诲侮。
(3)在Class文件格式中,特征簽名的范圍更大一些箱蟆,只要不是完全一樣的描述符就可以了沟绪,因此只有返回值不同也是可以算作重載的。因此同一個(gè)類中如果兩個(gè)類只有返回值不同時(shí)可以的空猜,因?yàn)檫@個(gè)類被編譯成.class字節(jié)碼文件后的描述符是不一樣的
七:總結(jié)
原本在方法表后就是屬性表了绽慈,但是還記得我在類文件結(jié)構(gòu)(上)一開篇提到的嗎?我是為了學(xué)習(xí)熱修補(bǔ)而開始學(xué)習(xí)java虛擬機(jī)的辈毯,因此關(guān)于屬性表和后面的知識(shí)坝疼,解析起來方法上是一樣的,為了讓我的目標(biāo)更明確谆沃,我決定類文件結(jié)構(gòu)就到此為止钝凶。
當(dāng)然,如果有比較多的朋友還想我寫屬性表和后面的內(nèi)容唁影,我會(huì)找個(gè)時(shí)間寫一遍的耕陷。
下一篇博客就是類加載機(jī)制了,這一塊是直接和熱修補(bǔ)技術(shù)相關(guān)的据沈,因此希望大家繼續(xù)耐心地支持我哟沫,謝謝。