深入理解JVM之Java字節(jié)碼(.class)文件詳解


Understanding bytecode makes you a better programmer

前言

作為一個Java開發(fā)者恰起,對技術(shù)的追求而不僅僅停留在會用API,會寫基本功能上淹魄,要想在技術(shù)上有更高的造詣充择,就需要深入到原理層面去認識代碼運行的機制搂根。因此铸敏,本文從class字節(jié)碼文件的結(jié)構(gòu)入手桑阶,一步步來解剖二進制字節(jié)碼的內(nèi)部工作原理唆樊,這對深入理解JVM的運行機制大有裨益霜运,同時擒贸,對于想要使用BCEL來動態(tài)改變Class字節(jié)碼指令的工作也很有幫助(示例:JVM Class字節(jié)碼之三-使用BCEL改變類屬性)。

什么是Class文件

Java字節(jié)碼類文件(.class)是Java編譯器編譯Java源文件(.java)產(chǎn)生的“目標(biāo)文件”觉渴。它是一種8位字節(jié)的二進制流文件介劫, 各個數(shù)據(jù)項按順序緊密的從前向后排列, 相鄰的項之間沒有間隙案淋, 這樣可以使得class文件非常緊湊座韵, 體積輕巧, 可以被JVM快速的加載至內(nèi)存踢京, 并且占據(jù)較少的內(nèi)存空間(方便于網(wǎng)絡(luò)的傳輸)誉碴。

Java源文件在被Java編譯器編譯之后, 每個類(或者接口)都單獨占據(jù)一個class文件瓣距, 并且類中的所有信息都會在class文件中有相應(yīng)的描述黔帕, 由于class文件很靈活, 它甚至比Java源文件有著更強的描述能力蹈丸。

class文件中的信息是一項一項排列的成黄, 每項數(shù)據(jù)都有它的固定長度, 有的占一個字節(jié)逻杖, 有的占兩個字節(jié)奋岁, 還有的占四個字節(jié)或8個字節(jié), 數(shù)據(jù)項的不同長度分別用u1, u2, u4, u8表示荸百, 分別表示一種數(shù)據(jù)項在class文件中占據(jù)一個字節(jié)闻伶, 兩個字節(jié), 4個字節(jié)和8個字節(jié)够话。 可以把u1, u2, u3, u4看做class文件數(shù)據(jù)項的“類型” 蓝翰。

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

一個典型的class文件分為:MagicNumber光绕,Version,Constant_pool畜份,Access_flag奇钞,This_class,Super_class漂坏,Interfaces景埃,F(xiàn)ields,Methods 和Attributes這十個部分顶别,用一個數(shù)據(jù)結(jié)構(gòu)可以表示如下:

class_code.PNG-21.1kB
class_code.PNG-21.1kB

下面對class文件中的每一項進行詳細的解釋:

1谷徙、magic
在class文件開頭的四個字節(jié), 存放著class文件的魔數(shù)驯绎, 這個魔數(shù)是class文件的標(biāo)志完慧,他是一個固定的值: 0XCAFEBABE 。 也就是說他是判斷一個文件是不是class格式的文件的標(biāo)準剩失, 如果開頭四個字節(jié)不是0XCAFEBABE屈尼, 那么就說明它不是class文件, 不能被JVM識別拴孤。

2脾歧、minor_version 和 major_version
緊接著魔數(shù)的四個字節(jié)是class文件的此版本號和主版本號。
隨著Java的發(fā)展演熟, class文件的格式也會做相應(yīng)的變動鞭执。 版本號標(biāo)志著class文件在什么時候, 加入或改變了哪些特性芒粹。 舉例來說兄纺, 不同版本的javac編譯器編譯的class文件, 版本號可能不同化漆, 而不同版本的JVM能識別的class文件的版本號也可能不同估脆, 一般情況下, 高版本的JVM能識別低版本的javac編譯器編譯的class文件座云, 而低版本的JVM不能識別高版本的javac編譯器編譯的class文件疙赠。 如果使用低版本的JVM執(zhí)行高版本的class文件, JVM會拋出java.lang.UnsupportedClassVersionError 疙教。具體的版本號變遷這里不再討論棺聊, 需要的讀者自行查閱資料。

3贞谓、constant_pool
在class文件中, 位于版本號后面的就是常量池相關(guān)的數(shù)據(jù)項葵诈。 常量池是class文件中的一項非常重要的數(shù)據(jù)裸弦。 常量池中存放了文字字符串祟同, 常量值, 當(dāng)前類的類名理疙, 字段名晕城, 方法名, 各個字段和方法的描述符窖贤, 對當(dāng)前類的字段和方法的引用信息砖顷, 當(dāng)前類中對其他類的引用信息等等。 常量池中幾乎包含類中的所有信息的描述赃梧, class文件中的很多其他部分都是對常量池中的數(shù)據(jù)項的引用滤蝠,比如后面要講到的this_class, super_class, field_info, attribute_info等, 另外字節(jié)碼指令中也存在對常量池的引用授嘀, 這個對常量池的引用當(dāng)做字節(jié)碼指令的一個操作數(shù)物咳。此外,常量池中各個項也會相互引用蹄皱。

常量池是一個類的結(jié)構(gòu)索引览闰,其它地方對“對象”的引用可以通過索引位置來代替,我們知道在程序中一個變量可以不斷地被調(diào)用巷折,要快速獲取這個變量常用的方法就是通過索引變量压鉴。這種索引我們可以直觀理解為“內(nèi)存地址的虛擬”。我們把它叫靜態(tài)池的意思就是說這里維護著經(jīng)過編譯“梳理”之后的相對固定的數(shù)據(jù)索引锻拘,它是站在整個JVM(進程)層面的共享池晴弃。

class文件中的項constant_pool_count的值為1, 說明每個類都只有一個常量池。 常量池中的數(shù)據(jù)也是一項一項的逊拍, 沒有間隙的依次排放上鞠。常量池中各個數(shù)據(jù)項通過索引來訪問, 有點類似與數(shù)組芯丧, 只不過常量池中的第一項的索引為1, 而不為0, 如果class文件中的其他地方引用了索引為0的常量池項芍阎, 就說明它不引用任何常量池項。class文件中的每一種數(shù)據(jù)項都有自己的類型缨恒, 相同的道理谴咸,常量池中的每一種數(shù)據(jù)項也有自己的類型。 常量池中的數(shù)據(jù)項的類型如下表:

constant_pool.PNG-25.4kB
constant_pool.PNG-25.4kB

每個數(shù)據(jù)項叫做一個XXX_info項骗露,比如岭佳,一個常量池中一個CONSTANT_Utf8類型的項,就是一個CONSTANT_Utf8_info 萧锉。除此之外珊随, 每個info項中都有一個標(biāo)志值(tag),這個標(biāo)志值表明了這個常量池中的info項的類型是什么, 從上面的表格中可以看出叶洞,一個CONSTANT_Utf8_info中的tag值為1鲫凶,而一個CONSTANT_Fieldref_info中的tag值為9 。

Java程序是動態(tài)鏈接的衩辟, 在動態(tài)鏈接的實現(xiàn)中螟炫, 常量池扮演者舉足輕重的角色。 除了存放一些字面量之外艺晴, 常量池中還存放著以下幾種符號引用:
(1) 類和接口的全限定名
(2) 字段的名稱和描述符
(3) 方法的名稱和描述符
我們有必要先了解一下class文件中的特殊字符串昼钻, 因為在常量池中, 特殊字符串大量的出現(xiàn)封寞,這些特殊字符串就是上面說的全限定名和描述符然评。對于常量池中的特殊字符串的了解,可以參考此文檔:Java class文件格式之特殊字符串_動力節(jié)點Java學(xué)院整理

4钥星、access_flag 保存了當(dāng)前類的訪問權(quán)限

5沾瓦、this_cass 保存了當(dāng)前類的全局限定名在常量池里的索引

6、super class 保存了當(dāng)前類的父類的全局限定名在常量池里的索引

7谦炒、interfaces 保存了當(dāng)前類實現(xiàn)的接口列表贯莺,包含兩部分內(nèi)容:interfaces_count 和interfaces[interfaces_count]
interfaces_count 指的是當(dāng)前類實現(xiàn)的接口數(shù)目
interfaces[] 是包含interfaces_count個接口的全局限定名的索引的數(shù)組

8、fields 保存了當(dāng)前類的成員列表宁改,包含兩部分的內(nèi)容:fields_count 和 fields[fields_count]
fields_count是類變量和實例變量的字段的數(shù)量總和缕探。
fileds[]是包含字段詳細信息的列表。

9还蹲、methods 保存了當(dāng)前類的方法列表爹耗,包含兩部分的內(nèi)容:methods_count和methods[methods_count]
methods_count是該類或者接口顯示定義的方法的數(shù)量。
method[]是包含方法信息的一個詳細列表谜喊。

10潭兽、attributes 包含了當(dāng)前類的attributes列表,包含兩部分內(nèi)容:attributes_count 和 attributes[attributes_count]
class文件的最后一部分是屬性斗遏,它描述了該類或者接口所定義的一些屬性信息山卦。attributes_count指的是attributes列表中包含的attribute_info的數(shù)量。
屬性可以出現(xiàn)在class文件的很多地方诵次,而不只是出現(xiàn)在attributes列表里账蓉。如果是attributes表里的屬性,那么它就是對整個class文件所對應(yīng)的類或者接口的描述逾一;如果出現(xiàn)在fileds的某一項里铸本,那么它就是對該字段額外信息的描述;如果出現(xiàn)在methods的某一項里遵堵,那么它就是對該方法額外信息的描述箱玷。

通過示例代碼來手動分析class文件

上面大致講解了一下class文件的結(jié)構(gòu),這里,我們拿一個class文件做一個簡單的分析汪茧,來驗證上面的文件結(jié)構(gòu)是否確實是如此椅亚。

我們在這里新建一個java文件限番,Hello.java舱污,具體內(nèi)容如下:

    public class Hello{
      private int test;
      public int test(){
            return test;
        }
    }

然后再通過javac命令將此java文件編譯成class文件:

javac /d/class_file_test/Hello.java

編譯之后的class文件十六進制結(jié)果如下所示,可以用UltraEdit等十六進制編輯器打開弥虐,得到:

hello_class_test.PNG-22.6kB
hello_class_test.PNG-22.6kB

接下來我們就按照class文件的格式來分析上面的一串?dāng)?shù)字扩灯,還是按照之前的順序來:

  1. magic:
    CA FE BA BE ,代表該文件是一個字節(jié)碼文件霜瘪,我們平時區(qū)分文件類型都是通過后綴名來區(qū)分的珠插,不過后綴名是可以隨便修改的,所以僅靠后綴名不能真正區(qū)分一個文件的類型颖对。區(qū)分文件類型的另個辦法就是magic數(shù)字捻撑,JVM 就是通過 CA FE BA BE 來判斷該文件是不是class文件

  2. version字段
    00 00 00 34,前兩個字節(jié)00是minor_version缤底,后兩個字節(jié)0034是major_version字段顾患,對應(yīng)的十進制值為52,也就是說當(dāng)前class文件的主版本號為52个唧,次版本號為0江解。下表是jdk 1.6 以后對應(yīng)支持的 Class 文件版本號:

    image_1c2th3nii1otd13vg1vhg1hl6itg4i.png-23.7kB
    image_1c2th3nii1otd13vg1vhg1hl6itg4i.png-23.7kB

  3. 常量池,constant_pool:
    3.1. constant_pool_count
    緊接著version字段下來的兩個字節(jié)是:00 12代表常量池里包含的常量數(shù)目徙歼,因為字節(jié)碼的常量池是從1開始計數(shù)的犁河,這個常量池包含17個(0x0012-1)常量。


    3.2.constant_pool
    接下來就是分析這17個常量:

3.2.1. 第一個變量 0a 00 04 00 0e
首先魄梯,緊接著constant_pool_count的第一個字節(jié)0a(tag=10)根據(jù)上面的表格(文中第二張圖片)

  ![image_1c2tj6ib6pslkbb1876ot81rjj4v.png-4kB][7]

可知桨螺,這表示的是一個CONSTANT_Methodref。CONSTANT_Methodref的結(jié)構(gòu)如下:
               CONSTANT_Methodref_info {
                         u1 tag;    //u1表示占一個字節(jié)
                         u2 class_index;    //u2表示占兩個字節(jié)
                         u2 name_and_type_index;    //u2表示占兩個字節(jié)
               }

其中class_index表示該方法所屬的類在常量池里的索引酿秸,name_and_type_index表示該方法的名稱和類型的索引灭翔。常量池里的變量的索引從1開始。

那么這個methodref結(jié)構(gòu)的數(shù)據(jù)如下:

                    0a  //tag  10表示這是一個CONSTANT_Methodref_info結(jié)構(gòu)
                    00 04 //class_index 指向常量池中第4個常量所表示的類
                    00 0e  //name_and_type_index 指向常量池中第14個常量所表示的方法

3.2.2. 第二個變量09 00 03 00 0F
接著是第二個常量允扇,它的tag是09缠局,根據(jù)上面的表格可知,這表示的是一個CONSTANT_Fieldref的結(jié)構(gòu)考润,它的結(jié)構(gòu)如下:

                    CONSTANT_Fieldref_info {
                         u1 tag;
                         u2 class_index;
                         u2 name_and_type_index;
                   }

和上面的變量基本一致狭园。

                        09 //tag
                        00 03 //指向常量池中第3個常量所表示的類
                        00 0f //指向常量池中第15個常量所表示的變量

3.2.3. 第三個變量 07 00 10
tag為07表示是一個CONSTANT_Class變量,這個變量的結(jié)構(gòu)如下:

                CONSTANT_Class_info {
                         u1 tag;
                         u2 name_index;
               }

除了tag字段以外糊治,還有一個name_index的值為00 10唱矛,即是指向常量池中第16個常量所表示的Class名稱。

3.2.4. 第四個變量07 00 11
同上,也是一個CONSTANT_Class變量绎谦,不過管闷,指向的是第17個常量所表示的Class名稱。

3.2.5. 第五個變量 01 00 04 74 65 73 74
tag為1窃肠,表示這是一個CONSTANT_Utf8結(jié)構(gòu)包个,這種結(jié)構(gòu)用UTF-8的一種變體來表示字符串,結(jié)構(gòu)如下所示:

                    CONSTANT_Utf8_info {
                                   u1 tag;
                                   u2 length;
                                   u1 bytes[length];
                    }

其中l(wèi)ength表示該字符串的字節(jié)數(shù)冤留,bytes字段包含該字符串的二進制表示碧囊。

                        01 //tag  1表示這是一個CONSTANT_Utf8結(jié)構(gòu)
                        00 04 //表示這個字符串的長度是4字節(jié),也就是后面的四個字節(jié)74 65 73 74
                        74 65 73 74 //通過ASCII碼表轉(zhuǎn)換后,表示的是字符串“test”

接下來的8個變量都是字符串纤怒,這里就不具體分析了糯而。

3.2.6. 第十四個常量 0c 00 07 00 08
tag為0c,表示這是一個CONSTANT_NameAndType結(jié)構(gòu)泊窘,這個結(jié)構(gòu)用來描述一個方法或者成員變量熄驼。具體結(jié)構(gòu)如下:

                    CONSTANT_NameAndType_info {
                              u1 tag;
                              u2 name_index;
                              u2 descriptor_index;
                    }

name_index表示的是該變量或者方法的名稱,這里的值是0007烘豹,表示指向第7個常量瓜贾,即是<init>

descriptor_index指向該方法的描述符的引用吴叶,這里的值是0008阐虚,表示指向第8個常量,即是()V蚌卤,由前面描述符的語法可知实束,這個方法是一個無參的,返回值為void的方法逊彭。

綜合兩個字段咸灿,可以推出這個方法是void <init>()。也即是指向這個NameAndType結(jié)構(gòu)的Methodref的方法名為void <init>()侮叮,也就是說第一個常量表示的是void <init>()方法避矢,這個方法其實就是此類的默認構(gòu)造方法。

3.2.7. 第十五個常量也是一個CONSTANT_NameAndType囊榜,表示的方法名為“int test()”审胸,第2個常量引用了這個NameAndType,所以第二個常量表示的是“int test()”方法卸勺。

3.2.8. 第16和17個常量也是字符串砂沛,可以按照前面的方法分析。

3.3. 完整的常量池
最后曙求,通過以上分析碍庵,完整的常量池如下:

          00 12  常量池的數(shù)目 18-1=17
          0a 00 04 00 0e  方法:java.lang.Ojbect void <init>()
          09 00 03 00 0f   方法 :Hello int test() 
          07 00 10  字符串:Hello
          07 00 11 字符串:java.lang.Ojbect
          01 00 04 74 65 73 74 字符串:test
          01 00 01 49  字符串:I
          01 00 06 3c 69 6e 69 74 3e 字符串:<init>
          01 00 03 28 29 56 字符串:()V
          01 00 04 43 6f 64 65 字符串:Code 
          01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 字符串:LineNumberTable 
          01 00 03 28 29 49 字符串:()I
          01 00 0a 53 6f 75 72 63 65 46 69 6c 65 字符串:SourceFile
          01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 字符串:Hello.java
          0c 00 07 00 08 NameAndType:<init> ()V
          0c 00 05 00 06 NameAndType:test I
          01 00 05 48 65 6c 6c 6f 字符串:Hello
          01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 字符串: java/lang/Object

通過這樣分析其實非常的累映企,我們只是為了了解class文件的原理才來一步一步分析每一個二進制字節(jié)碼。JDK提供了現(xiàn)成的工具可以直接解析此二進制文件静浴,即javap工具(在JDK的bin目錄下)堰氓,我們通過javap命令來解析此class文件:

javap -v -p -s -sysinfo -constants /d/class_file_test/Hello.class

解析得到的結(jié)果為:

class_file_2.PNG-44.1kB
class_file_2.PNG-44.1kB

關(guān)于此表每一項的詳細分析,可以參考國外的這一篇文檔:JVM Internals
關(guān)于此表中Method操作指令aload_1,getfield,ireturn的作用苹享,可以參考云溪社區(qū)的這篇文章:
JVM Class詳解之二 Method字節(jié)碼指令

發(fā)現(xiàn)了沒有双絮,上面生成代碼中的Constant pool跟我們上面分析出來的完整常量池一模一樣,有木有富稻!有木有掷邦?
這說明我們上面的分析的完成正確白胀!

由此椭赋,我們終于弄懂了Constant pool的內(nèi)幕。

接下來繼續(xù)看其他的字段或杠。

  1. access_flag(u2)
    00 21這兩個字節(jié)的數(shù)據(jù)表示這個變量的訪問標(biāo)志位哪怔,JVM對訪問標(biāo)示符的規(guī)范如下:
access_flag.PNG-26.8kB
access_flag.PNG-26.8kB

這個表里面無法直接查詢到0021這個值,原因是0021=0020+0001向抢,也就是表示當(dāng)前class的access_flag是ACC_PUBLIC|ACC_SUPER认境。ACC_PUBLIC和代碼里的public 關(guān)鍵字相對應(yīng)挟鸠。ACC_SUPER表示當(dāng)用invokespecial指令來調(diào)用父類的方法時需要特殊處理艘希。

  1. this_class(u2) 00 03
    this_class指向constant pool的索引值,該值必須是CONSTANT_Class_info類型佳遂,這里是3丑罪,即指向常量池中的第三項吩屹,即是“Hello”煤搜。

  2. super_class 00 04
    super_class存的是父類的名稱在常量池里的索引徙鱼,這里指向第四個常量,即是“java/lang/Object”距淫。

  3. interfaces
    interfaces包含interfaces_count和interfaces[]兩個字段婶希。因為這里沒有實現(xiàn)接口喻杈,所以就不存在interfces選項筒饰,所以這里的interfaces_count為0(0000)瓷们,所以后面的內(nèi)容也對應(yīng)為空谬晕。

  4. fields
               00 01 fields count        //表示成員變量的個數(shù)攒钳,此處為1個
               00 02 00 05 00 06 00 00   //成員變量的結(jié)構(gòu)

每個成員變量對應(yīng)一個field_info結(jié)構(gòu):

               field_info {
                         u2 access_flags; 0002
                         u2 name_index; 0005
                         u2 descriptor_index; 0006
                         u2 attributes_count; 0000
                         attribute_info attributes[attributes_count];
               }

access_flags為0002不撑,即是ACC_PRIVATE
name_index指向常量池的第五個常量燎孟,為“test”
descriptor_index指向常量池的第6個常量為“I”
三個字段結(jié)合起來揩页,說明這個變量是"private int test"爆侣。
接下來的是attribute字段,用來描述該變量的屬性茫负,因為這個變量沒有附加屬性乎赴,所以attributes_count為0,attribute_info為空勉失。

  1. methods
    00 02 00 01 00 07 00 08 00 01 00 09 ...
    最前面的2個字節(jié)是method_count
    method_count:00 02原探,為什么會有兩個方法呢?我們明明只寫了一個方法徒蟆,這是因為JVM 會自動生成一個<init>方法段审,這個是類的默認構(gòu)造方法输莺。

接下來的內(nèi)容是兩個method_info結(jié)構(gòu):

                    method_info {
                         u2 access_flags;
                         u2 name_index;
                         u2 descriptor_index;
                         u2 attributes_count;
                         attribute_info attributes[attributes_count];
                    }

前三個字段和field_info一樣嫂用,可以分析出第一個方法是“public void <init>()”

                         00 01 ACC_PUBLIC
                         00 07  <init>
                         00 08  V()

接下來是attribute字段嘱函,也即是這個方法的附加屬性往弓,這里的attributes_count =1函似,也即是有一個屬性撇寞。
每個屬性的都是一個attribute_info結(jié)構(gòu)堂氯,如下所示:

                    attribute_info {
                         u2 attribute_name_index;
                         u4 attribute_length;
                         u1 info[attribute_length];
                    }

JVM預(yù)定義了部分attribute咽白,但是編譯器自己也可以實現(xiàn)自己的attribute寫入class文件里晶框,供運行時使用。不同的attribute通過attribute_name_index來區(qū)分。JVM規(guī)范里對以下attribute進行了預(yù)定義:

21718047_1346754834pJjH.png-65.4kB
21718047_1346754834pJjH.png-65.4kB

這里的attribute_name_index值為0009,表示指向第9個常量,即是Code牺汤。Code Attribute的作用是保存該方法的結(jié)構(gòu)如所對應(yīng)的字節(jié)碼,具體的結(jié)構(gòu)如下所示:

     Code_attribute {
          u2 attribute_name_index;
          u4 attribute_length;
          u2 max_stack;
          u2 max_locals;
          u4 code_length;
          u1 code[code_length];
          u2 exception_table_length;
          { 
               u2 start_pc;
               u2 end_pc;
               u2 handler_pc;
               u2 catch_type;
          } exception_table[exception_table_length];
          u2 attributes_count;
          attribute_info attributes[attributes_count];
     }

attribute_length表示attribute所包含的字節(jié)數(shù)浩嫌,這里為0000001d檐迟,即是39個字節(jié),不包含attribute_name_index和attribute_length字段码耐。
max_stack表示這個方法運行的任何時刻所能達到的操作數(shù)棧的最大深度追迟,這里是0001
max_locals表示方法執(zhí)行期間創(chuàng)建的局部變量的數(shù)目,包含用來表示傳入的參數(shù)的局部變量骚腥,這里是0001.
接下來的code_length表示該方法的所包含的字節(jié)碼的字節(jié)數(shù)以及具體的指令碼敦间。
這里的字節(jié)碼長度為00000005,即是后面的5個字節(jié) 2a b7 00 01 b1為對應(yīng)的字節(jié)碼指令的指令碼束铭。
參照下表可以將上面的指令碼翻譯成對應(yīng)的助記符:

               2a   aload_0    
               b7   invokespecial
               00   nop
               01   aconst_null
               b1   return

這即是該方法被調(diào)用時廓块,虛擬機所執(zhí)行的字節(jié)碼

接下來是exception_table,這里存放的是處理異常的信息契沫。
每個exception_table表項由start_pc带猴,end_pc,handler_pc懈万,catch_type組成拴清。start_pc和end_pc表示在code數(shù)組中的從start_pc到end_pc處(包含start_pc,不包含end_pc)的指令拋出的異常會由這個表項來處理;handler_pc表示處理異常的代碼的開始處钞速。catch_type表示會被處理的異常類型贷掖,它指向常量池里的一個異常類。當(dāng)catch_type為0時渴语,表示處理所有的異常苹威,這個可以用來實現(xiàn)finally的功能。

不過驾凶,這段代碼里沒有異常處理牙甫,所以exception_table_length為0000掷酗,所以我們不做分析。

接下來是該方法的附加屬性窟哺,attributes_count為0001泻轰,表示有一個附加屬性。
attribute_name_index為000a且轨,指向第十個常量浮声,為LineNumberTable。這個屬性用來表示code數(shù)組中的字節(jié)碼和java代碼行數(shù)之間的關(guān)系旋奢。這個屬性可以用來在調(diào)試的時候定位代碼執(zhí)行的行數(shù)泳挥。LineNumberTable的結(jié)構(gòu)如下:

     LineNumberTable_attribute {
               u2 attribute_name_index;
               u4 attribute_length;
               u2 line_number_table_length;
               { u2 start_pc;
               u2 line_number;
          } line_number_table[line_number_table_length];
     }

前面兩個字段分別表示這個attribute的名稱是LineNumberTable以及長度為00000006。接下來的0001表示line_number_table_length至朗,表示line_number_table有一個表項屉符,其中start_pc為 00 00,line_number為 00 00锹引,表示第0行代碼從code的第0個指令碼開始矗钟。

后面的內(nèi)容是第二個方法,具體就不再分析了嫌变。

  1. attributes
    最后剩下的內(nèi)容是attributes吨艇,這里的attributes表示整個class文件的附加屬性,不過結(jié)構(gòu)還是和前面的attribute保持一致初澎。00 01表示有一個attribute秸应。
    Attribute結(jié)構(gòu)如下:
          SourceFile_attribute {
               u2 attribute_name_index;
               u4 attribute_length;
               u2 sourcefile_index;
          }

attribute_name_index為000c,指向第12個常量碑宴,為SourceFile,說明這個屬性是Source
attribute_length為00000002
sourcefile_index為000d桑谍,表示指向常量池里的第13個常量延柠,為Hello.java
這個屬性表明當(dāng)前的class文件是從Hello.java文件編譯而來锣披。

字節(jié)碼修改技術(shù)

對Java Class字節(jié)碼分析贞间,我們應(yīng)該能夠比較清楚的認識到整個字節(jié)碼的結(jié)構(gòu)。

那通過了解字節(jié)碼雹仿,我們可以做些什么呢增热?

其實通過字節(jié)碼能做很多平時我們無法完成的工作。比如胧辽,在類加載之前添加某些操作或者直接動態(tài)的生成字節(jié)峻仇。

ASM 是一個 Java 字節(jié)碼操控框架。它能夠以二進制形式修改已有類或者動態(tài)生成類邑商。ASM 可以直接產(chǎn)生二進制 class 文件摄咆,也可以在類被加載入 Java 虛擬機之前動態(tài)改變類行為凡蚜。ASM 從類文件中讀入信息后,能夠改變類行為吭从,分析類信息朝蜘,甚至能夠根據(jù)用戶要求生成新類。不過ASM在創(chuàng)建class字節(jié)碼的過程中涩金,操縱的級別是底層JVM的匯編指令級別谱醇,這要求ASM使用者要對class組織結(jié)構(gòu)和JVM匯編指令有一定的了解。

目前字節(jié)碼修改技術(shù)有ASM步做,javassist副渴,cglib,BCEL等辆床。cglib就是基于封裝的Asm. Spring 就是使用cglib代理庫佳晶。關(guān)于cglib的使用介紹,可以參考:CGLIB介紹與原理

Javassist是一個開源的分析讼载、編輯和創(chuàng)建Java字節(jié)碼的類庫轿秧。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計算機科學(xué)系的 Shigeru Chiba (千葉 滋)所創(chuàng)建的。它已加入了開放源代碼JBoss 應(yīng)用服務(wù)器項目,通過使用Javassist對字節(jié)碼操作為JBoss實現(xiàn)動態(tài)AOP框架咨堤。javassist是jboss的一個子項目菇篡,其主要的優(yōu)點,在于簡單一喘,而且快速驱还。直接使用java編碼的形式,而不需要了解虛擬機指令凸克,就能動態(tài)改變類的結(jié)構(gòu)议蟆,或者動態(tài)生成類。

參考文檔

  1. Java Code to Byte Code
  2. JVM Internals
  3. 從字節(jié)碼層面看“HelloWorld”
  4. ASM官網(wǎng)
  5. CGLIB介紹與原理
  6. CGLIB(Code Generation Library)詳解
  7. JVM之字節(jié)碼——Class文件格式
  8. 云溪社區(qū)--JVM Class詳解之一
  9. 云溪社區(qū)--JVM Class字節(jié)碼之三-使用BCEL改變類屬性
  10. 國外翻譯文章:Java 編程的動態(tài)性萎战,用 BCEL 設(shè)計字節(jié)碼
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咐容,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蚂维,更是在濱河造成了極大的恐慌戳粒,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虫啥,死亡現(xiàn)場離奇詭異蔚约,居然都是意外死亡,警方通過查閱死者的電腦和手機涂籽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門苹祟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事苔咪∶痰浚” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵团赏,是天一觀的道長箕般。 經(jīng)常有香客問我,道長舔清,這世上最難降的妖魔是什么丝里? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮体谒,結(jié)果婚禮上杯聚,老公的妹妹穿的比我還像新娘。我一直安慰自己抒痒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布傀广。 她就那樣靜靜地躺著彩届,像睡著了一般伪冰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上樟蠕,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天寨辩,我揣著相機與錄音吓懈,去河邊找鬼。 笑死靡狞,一個胖子當(dāng)著我的面吹牛骄瓣,可吹牛的內(nèi)容都是我干的耍攘。 我是一名探鬼主播畔勤,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庆揪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起兰伤,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤钧排,失蹤者是張志新(化名)和其女友劉穎恨溜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體糟袁,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡项戴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年周叮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片则吟。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖水慨,靈堂內(nèi)的尸體忽然破棺而出敬扛,到底是詐尸還是另有隱情,我是刑警寧澤谍珊,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布急侥,位于F島的核電站坏怪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铝宵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一亡笑、第九天 我趴在偏房一處隱蔽的房頂上張望横朋。 院中可真熱鬧,春花似錦绝骚、人聲如沸祠够。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽落君。三九已至,卻和暖如春皮获,著一層夾襖步出監(jiān)牢的瞬間纹冤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工雁歌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留知残,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓乏盐,卻偏偏與公主長得像制恍,于是被迫代替她去往敵國和親吧趣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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