class字節(jié)碼文件解析

文件結(jié)構(gòu)

Java class文件是8位字節(jié)的二進(jìn)制流萄唇,數(shù)據(jù)項(xiàng)按順序存儲(chǔ)在class文件中,相鄰的項(xiàng)之間沒有任何間隔,這樣可以使class文件緊湊姓赤。占據(jù)多個(gè)字節(jié)空間的項(xiàng)按照高位在前的順序分為幾個(gè)連續(xù)的字節(jié)存放。在class文件中杉辙,可變長度項(xiàng)的大小和長度位于其實(shí)際數(shù)據(jù)之前模捂。這個(gè)特性使得class文件流可以從頭到尾被順序解析,首先讀出項(xiàng)的大小蜘矢,然后讀出項(xiàng)的數(shù)據(jù)狂男。Class文件中有兩種數(shù)據(jù)結(jié)構(gòu):無符號(hào)數(shù)和表∑犯梗可以對(duì)比xml岖食、json,二進(jìn)制文件沒有空格和換行舞吭,節(jié)省空間泡垃,提高性能,但放棄了可讀性羡鸥。

image.png

魔數(shù)

每個(gè)Java class文件的前4個(gè)字節(jié)被稱為它的魔數(shù)(magic number):0xCAFEBABE蔑穴。魔數(shù)的作用在于,可以輕松地分辨出Java class文件和非Java class文件惧浴。

class文件的下面4個(gè)字節(jié)包含了主存和、次版本號(hào)。對(duì)于Java虛擬機(jī)來說衷旅,版本號(hào)確定了特定的class文件格式捐腿,通常只有給定主版本號(hào)和一系列次版本號(hào)后,Java虛擬機(jī)才能夠讀取class文件柿顶。如52對(duì)應(yīng)JDK1.8茄袖。

常量池

constant_pool_count和constant_pool

constant_pool_count:兩個(gè)字節(jié)表示常量池的長度,編號(hào)從1開始嘁锯;
CP_info:每個(gè)常量池入口都從一個(gè)長度為一個(gè)字節(jié)的標(biāo)志開始(tag)宪祥,這個(gè)標(biāo)志指出了列表中該位置的常量類型。JDK 1.7以后共有14種不相同表結(jié)構(gòu)的數(shù)據(jù)家乘。

image.png

訪問標(biāo)志access_flags

緊接常量池后的兩個(gè)字節(jié)稱為access_flags品山,它展示了文件中定義的類或接口的幾段信息,包括這個(gè)Class是類還是接口烤低;是否定義為public類型肘交;是否為abstrct類型;在access_flags中所有未使用的位都必須由編譯器置0扑馁,而且Java虛擬機(jī)必須忽略它涯呻。


enter description here

類索引

接下來的兩個(gè)字節(jié)為this_class項(xiàng)凉驻,它是一個(gè)對(duì)常量池的索引。在this_class位置的常量池入口必須為CONSTANT_Class_info表复罐。該表由兩個(gè)部分組成——標(biāo)簽和name_index涝登。標(biāo)簽部分是一個(gè)具有CONSTANT_Class值的常量,在name_index位置的常量池入口為一個(gè)包含了類或接口全限定名的CONSTANT_Utf8_info表效诅。

enter description here

父類索引與接口索引集合同理胀滚。

字段表集合

緊接在interfaces后面的是對(duì)在該類或者接口中所聲明的字段的描述。首先是名為fields_count的計(jì)數(shù)乱投,它是類變量和實(shí)例變量的字段的數(shù)量總和咽笼。在這個(gè)計(jì)數(shù)后面的是不同長度的field_info表的序列(fields_count指出了序列中有多少個(gè)field_info表)。在fields列表中戚炫,不列出從超類或者父接口繼承而來的字段剑刑。字段表結(jié)構(gòu)如下圖所示:

enter description here

方法表集合

緊接著fields后面的是對(duì)在該類或者接口中所聲明的方法的描述。只包括在該類或者接口中顯式定義的方法双肤。

image.png

屬性表集合

在Class文件施掏、字段表、方法表中都可以攜帶自己的屬性表集合茅糜。相對(duì)于其它表七芭,屬性表的限制相對(duì)較小,不再要求各個(gè)屬性表有嚴(yán)格的順序蔑赘,可以寫入自定義的屬性信息狸驳,JVM也預(yù)定義了21項(xiàng)屬性表。對(duì)于每個(gè)屬性米死,它的名稱需要從常量池中引入一個(gè)CONSTANT_Utf8_info類型的常量來表示,而屬性值的結(jié)構(gòu)則完全自定義贮庞,只需要一個(gè)u4的長度屬性去說明屬性值所占用的位數(shù)即可峦筒,一個(gè)屬性表結(jié)構(gòu)如下圖所示:

enter description here

字節(jié)碼指令

Java虛擬機(jī)的指令由一個(gè)字節(jié)長度的、代表著某種特定操作含義的數(shù)字(稱為操作碼窗慎,Opcode)以及跟隨其后的零至多個(gè)代表此操作所需參數(shù)(稱為操作數(shù)物喷,Operands)而構(gòu)成。操作碼的長度為1個(gè)字節(jié)遮斥,因此最大只有256條峦失,是基于的指令集架構(gòu)。

字節(jié)碼與數(shù)據(jù)類型

在Java虛擬機(jī)的指令集中术吗,大多數(shù)的指令都包含了其操作所對(duì)應(yīng)的數(shù)據(jù)類型信息尉辑。iload中的i表示的是int。i代表對(duì)int類型的數(shù)據(jù)操作较屿,l代表long隧魄,s代表short卓练,b代表byte,c代表char购啄,f代表float襟企,d代表double,a代表reference狮含。也有不包含類型信息的:goto與類型無關(guān)顽悼;Arraylength操作數(shù)組類型。

加載與存儲(chǔ)指令

加載和存儲(chǔ)指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來回傳輸几迄,這類指令包括:

  1. 將一個(gè)局部變量加載到操作棧:iload
  2. 將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:istore
  3. 將一個(gè)常量加載到操作數(shù)棧:bipush蔚龙。
  4. 擴(kuò)充局部變量表的訪問索引的指令:wide
    enter description here

運(yùn)算指令

運(yùn)算或算術(shù)指令用于對(duì)兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定運(yùn)算,并把結(jié)果重新存入到操作棧頂乓旗。大體上算術(shù)指令可以分為兩種:對(duì)整型數(shù)據(jù)進(jìn)行運(yùn)算的指令與對(duì)浮點(diǎn)型數(shù)據(jù)進(jìn)行運(yùn)算的指令府蛇,無論是哪種算術(shù)指令,都使用Java虛擬機(jī)的數(shù)據(jù)類型屿愚,由于沒有直接支持byte汇跨、short、char和boolean類型的算術(shù)指令妆距,對(duì)于這類數(shù)據(jù)的運(yùn)算穷遂,應(yīng)使用操作int類型的指令代替。
注:e = a + b + c + d +e娱据,操作數(shù)棧的深度依然是2蚪黑。

類型轉(zhuǎn)換指令

類型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類型進(jìn)行相互轉(zhuǎn)換,這些轉(zhuǎn)換操作一般用于實(shí)現(xiàn)用戶代碼中的顯示類型轉(zhuǎn)換操作中剩,或者用來處理字節(jié)碼指令中數(shù)據(jù)類型相關(guān)指令無法與數(shù)據(jù)類型一一對(duì)應(yīng)的問題忌穿。

寬化類型處理:int i= 1;long l = i结啼;
窄化類型處理:User user = new User(); Object obj = user;
處理窄化類型轉(zhuǎn)換時(shí)掠剑,必須顯式地使用轉(zhuǎn)換指令來完成,這些轉(zhuǎn)換指令包括:i2b郊愧、i2c朴译、i2s、l2i属铁、f2i眠寿、f2l、d2i焦蘑、d2l和d2f盯拱。

對(duì)象創(chuàng)建與訪問指令

雖然類實(shí)例和數(shù)組都是對(duì)象,但Java虛擬機(jī)對(duì)類實(shí)例和數(shù)組的創(chuàng)建與操作使用了不同的字節(jié)碼指令。對(duì)象創(chuàng)建后坟乾,就可以通過對(duì)象訪問指令獲取對(duì)象實(shí)例或者數(shù)組實(shí)例中的字段或者數(shù)組元素迹辐,這些指令如下:

  • 創(chuàng)建類實(shí)例的指令:new;
  • 創(chuàng)建數(shù)組的指令:newarray、anewarray、multianewarray凌唬;
  • 訪問類字段(static字段蒲肋,或者稱為類變量)和實(shí)例字段(非static字段,或者稱為實(shí)例變量)的指令:getstatic、putstatic、getfield、putfield仍律;
  • 把一個(gè)數(shù)組元素加載到操作數(shù)棧的指令:baload、caload实柠、saload水泉、iaload、laload窒盐、faload草则、daload、aaload蟹漓;
  • 將一個(gè)操作數(shù)棧的值存儲(chǔ)到數(shù)組元素中的指令:bastore炕横、castore、sastore葡粒、iastore份殿、fastore、dastore嗽交、aastore卿嘲;
  • 取數(shù)組長度的指令:arraylength;
  • 檢查類實(shí)例類型的指令:instanceof夫壁、checkcast拾枣。
class Demo 
{
    public static void main(String[ ] args){
        User user = new User();
        User[] users = new User[10];
        int[] is = new int[10];


        user.name = "hello";
        String username = user.name;
    }
}

class User{
    String name;
    static int age;
}

字節(jié)碼指令為:

Code:
      stack=2, locals=5, args_size=1
         0: new           #2                  // class User
         3: dup
         4: invokespecial #3                  // Method User."<init>":()V
         7: astore_1
         8: bipush        10
        10: anewarray     #2                  // class User
        13: astore_2
        14: bipush        10
        16: newarray       int
        18: astore_3
        19: aload_1
        20: ldc           #4                  // String hello
        22: putfield      #5                  // Field User.name:Ljava/lang/String;
        25: aload_1
        26: getfield      #5                  // Field User.name:Ljava/lang/String;
        29: astore        4
        31: return

1234567891011121314151617181920

操作數(shù)棧管理指令

如同操作一個(gè)普通數(shù)據(jù)結(jié)構(gòu)中的堆棧那樣,Java虛擬機(jī)提供了一些用于直接操作操作數(shù)棧的指令掌唾,包括:

  • 將操作數(shù)棧的棧頂一個(gè)或兩個(gè)元素出棧:pop放前、pop2忿磅;(不常用)
  • 復(fù)制棧頂一個(gè)或兩個(gè)數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup糯彬、dup2、dup_x1葱她、dup2_x1撩扒、dup_x2、dup2_x2;
  • 將棧最頂端的兩個(gè)數(shù)值互換:swap搓谆。

控制轉(zhuǎn)移指令

控制轉(zhuǎn)移指令可以讓Java虛擬機(jī)有條件或無條件地從指定的位置指令而不是控制轉(zhuǎn)移指令的下一條指令繼續(xù)執(zhí)行程序炒辉,從概念模型上理解,可以認(rèn)為控制轉(zhuǎn)移指令就是在有條件或無條件地修改PC寄存器的值泉手。如:goto等黔寇。

class Demo {
    public static void main(String[ ] args){
        int a = 10;
        if(a > 10){
            System.out.println(">10");
        }else{
            System.out.println(">=10");
        }
    }
}

字節(jié)碼指令為:

Code:
      stack=2, locals=2, args_size=1
         0: bipush        10
         2: istore_1
         3: iload_1
         4: bipush        10
         6: if_icmple     20
         9: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        12: ldc           #3                  // String >10
        14: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        17: goto          28
        20: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        23: ldc           #5                  // String >=10
        25: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V


方法調(diào)用

  • invokevirtual:用于調(diào)用對(duì)象的實(shí)例方法,根據(jù)對(duì)象的實(shí)際類型進(jìn)行分派(虛方法分派)斩萌,這也是Java語言中最常見的方法分派方式缝裤。
  • invokeinterface:用于調(diào)用接口方法,它會(huì)在運(yùn)行時(shí)搜索一個(gè)實(shí)現(xiàn)了這個(gè)接口方法的對(duì)象颊郎,找出適合的方法進(jìn)行調(diào)用憋飞。
  • invokespecial:用于調(diào)用一些需要特殊處理的實(shí)例方法,包括實(shí)例初始化方法姆吭、私有方法和父類方法榛做。
  • invokestatic:用于調(diào)用類方法(static 方法)

異常處理指令

在Java程序中顯示拋出異常的操作(throw語句)都由athrow指令來實(shí)現(xiàn),除了用throw語句顯示拋出異常情況之外内狸,Java虛擬機(jī)規(guī)范還規(guī)定了許多運(yùn)行時(shí)異常會(huì)在其他Java虛擬機(jī)指令檢測(cè)到異常狀況時(shí)自動(dòng)拋出检眯。而在Java虛擬機(jī)中,處理異常(catch語句)不是由字節(jié)碼指令來實(shí)現(xiàn)的答倡,而是采用異常表來完成的轰传。

class Demo 
{
    public static void main(String[ ] args){
        int a = 0;
        throw new RuntimeException("Exception......");
    }
}

12345678

字節(jié)碼指令為:

Code:
      stack=3, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: new           #2                  // class java/lang/RuntimeException
         5: dup
         6: ldc           #3                  // String Exception......
         8: invokespecial #4                  // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
        11: athrow

12345678910

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瘪撇,隨后出現(xiàn)的幾起案子获茬,更是在濱河造成了極大的恐慌,老刑警劉巖倔既,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恕曲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡渤涌,警方通過查閱死者的電腦和手機(jī)佩谣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來实蓬,“玉大人茸俭,你說我怎么就攤上這事“仓澹” “怎么了调鬓?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酌伊。 經(jīng)常有香客問我腾窝,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任虹脯,我火速辦了婚禮驴娃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘循集。我一直安慰自己唇敞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布咒彤。 她就那樣靜靜地躺著厚棵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔼紧。 梳的紋絲不亂的頭發(fā)上婆硬,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天,我揣著相機(jī)與錄音奸例,去河邊找鬼彬犯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛查吊,可吹牛的內(nèi)容都是我干的谐区。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼逻卖,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宋列!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起评也,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤炼杖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后盗迟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坤邪,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年罚缕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艇纺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邮弹,死狀恐怖黔衡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腌乡,我是刑警寧澤盟劫,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站导饲,受9級(jí)特大地震影響捞高,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜渣锦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一硝岗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袋毙,春花似錦型檀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至皆看,卻和暖如春仓坞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腰吟。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工无埃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人毛雇。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓嫉称,卻偏偏與公主長得像,于是被迫代替她去往敵國和親灵疮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子织阅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容