前言
作為一個(gè)Java開(kāi)發(fā)者,對(duì)技術(shù)的追求而不僅僅停留在會(huì)用API,會(huì)寫(xiě)基本功能上,要想在技術(shù)上有更高的造詣,就需要深入到原理層面去認(rèn)識(shí)代碼運(yùn)行的機(jī)制。因此,本文從class字節(jié)碼文件的結(jié)構(gòu)入手,一步步來(lái)解剖二進(jìn)制字節(jié)碼的內(nèi)部工作原理,這對(duì)深入理解JVM的運(yùn)行機(jī)制大有裨益,同時(shí),對(duì)于想要使用BCEL來(lái)動(dòng)態(tài)改變Class字節(jié)碼指令的工作也很有幫助(示例:JVM Class字節(jié)碼之三-使用BCEL改變類屬性)。
什么是Class文件
Java字節(jié)碼類文件(.class)是Java編譯器編譯Java源文件(.java)產(chǎn)生的“目標(biāo)文件”。它是一種8位字節(jié)的二進(jìn)制流文件, 各個(gè)數(shù)據(jù)項(xiàng)按順序緊密的從前向后排列, 相鄰的項(xiàng)之間沒(méi)有間隙, 這樣可以使得class文件非常緊湊, 體積輕巧, 可以被JVM快速的加載至內(nèi)存, 并且占據(jù)較少的內(nèi)存空間(方便于網(wǎng)絡(luò)的傳輸)。
Java源文件在被Java編譯器編譯之后, 每個(gè)類(或者接口)都單獨(dú)占據(jù)一個(gè)class文件, 并且類中的所有信息都會(huì)在class文件中有相應(yīng)的描述, 由于class文件很靈活, 它甚至比Java源文件有著更強(qiáng)的描述能力。
class文件中的信息是一項(xiàng)一項(xiàng)排列的, 每項(xiàng)數(shù)據(jù)都有它的固定長(zhǎng)度, 有的占一個(gè)字節(jié), 有的占兩個(gè)字節(jié), 還有的占四個(gè)字節(jié)或8個(gè)字節(jié), 數(shù)據(jù)項(xiàng)的不同長(zhǎng)度分別用u1, u2, u4, u8表示, 分別表示一種數(shù)據(jù)項(xiàng)在class文件中占據(jù)一個(gè)字節(jié), 兩個(gè)字節(jié), 4個(gè)字節(jié)和8個(gè)字節(jié)。 可以把u1, u2, u3, u4看做class文件數(shù)據(jù)項(xiàng)的“類型” 。
Class文件的結(jié)構(gòu)
一個(gè)典型的class文件分為:MagicNumber,Version,Constant_pool,Access_flag,This_class,Super_class,Interfaces,F(xiàn)ields,Methods 和Attributes這十個(gè)部分,用一個(gè)數(shù)據(jù)結(jié)構(gòu)可以表示如下:
下面對(duì)class文件中的每一項(xiàng)進(jìn)行詳細(xì)的解釋:
1、magic
在class文件開(kāi)頭的四個(gè)字節(jié), 存放著class文件的魔數(shù), 這個(gè)魔數(shù)是class文件的標(biāo)志,他是一個(gè)固定的值: 0XCAFEBABE 。 也就是說(shuō)他是判斷一個(gè)文件是不是class格式的文件的標(biāo)準(zhǔn), 如果開(kāi)頭四個(gè)字節(jié)不是0XCAFEBABE, 那么就說(shuō)明它不是class文件, 不能被JVM識(shí)別。
2、minor_version 和 major_version
緊接著魔數(shù)的四個(gè)字節(jié)是class文件的此版本號(hào)和主版本號(hào)。
隨著Java的發(fā)展, class文件的格式也會(huì)做相應(yīng)的變動(dòng)。 版本號(hào)標(biāo)志著class文件在什么時(shí)候, 加入或改變了哪些特性。 舉例來(lái)說(shuō), 不同版本的javac編譯器編譯的class文件, 版本號(hào)可能不同, 而不同版本的JVM能識(shí)別的class文件的版本號(hào)也可能不同, 一般情況下, 高版本的JVM能識(shí)別低版本的javac編譯器編譯的class文件, 而低版本的JVM不能識(shí)別高版本的javac編譯器編譯的class文件。 如果使用低版本的JVM執(zhí)行高版本的class文件, JVM會(huì)拋出java.lang.UnsupportedClassVersionError 。具體的版本號(hào)變遷這里不再討論, 需要的讀者自行查閱資料。
3、constant_pool
在class文件中, 位于版本號(hào)后面的就是常量池相關(guān)的數(shù)據(jù)項(xiàng)。 常量池是class文件中的一項(xiàng)非常重要的數(shù)據(jù)。 常量池中存放了文字字符串, 常量值, 當(dāng)前類的類名, 字段名, 方法名, 各個(gè)字段和方法的描述符, 對(duì)當(dāng)前類的字段和方法的引用信息, 當(dāng)前類中對(duì)其他類的引用信息等等。 常量池中幾乎包含類中的所有信息的描述, class文件中的很多其他部分都是對(duì)常量池中的數(shù)據(jù)項(xiàng)的引用,比如后面要講到的this_class, super_class, field_info, attribute_info等, 另外字節(jié)碼指令中也存在對(duì)常量池的引用, 這個(gè)對(duì)常量池的引用當(dāng)做字節(jié)碼指令的一個(gè)操作數(shù)。此外,常量池中各個(gè)項(xiàng)也會(huì)相互引用。
常量池是一個(gè)類的結(jié)構(gòu)索引,其它地方對(duì)“對(duì)象”的引用可以通過(guò)索引位置來(lái)代替,我們知道在程序中一個(gè)變量可以不斷地被調(diào)用,要快速獲取這個(gè)變量常用的方法就是通過(guò)索引變量。這種索引我們可以直觀理解為“內(nèi)存地址的虛擬”。我們把它叫靜態(tài)池的意思就是說(shuō)這里維護(hù)著經(jīng)過(guò)編譯“梳理”之后的相對(duì)固定的數(shù)據(jù)索引,它是站在整個(gè)JVM(進(jìn)程)層面的共享池。
class文件中的項(xiàng)constant_pool_count的值為1, 說(shuō)明每個(gè)類都只有一個(gè)常量池。 常量池中的數(shù)據(jù)也是一項(xiàng)一項(xiàng)的, 沒(méi)有間隙的依次排放。常量池中各個(gè)數(shù)據(jù)項(xiàng)通過(guò)索引來(lái)訪問(wèn), 有點(diǎn)類似與數(shù)組, 只不過(guò)常量池中的第一項(xiàng)的索引為1, 而不為0, 如果class文件中的其他地方引用了索引為0的常量池項(xiàng), 就說(shuō)明它不引用任何常量池項(xiàng)。class文件中的每一種數(shù)據(jù)項(xiàng)都有自己的類型, 相同的道理,常量池中的每一種數(shù)據(jù)項(xiàng)也有自己的類型。 常量池中的數(shù)據(jù)項(xiàng)的類型如下表:
每個(gè)數(shù)據(jù)項(xiàng)叫做一個(gè)XXX_info項(xiàng),比如,一個(gè)常量池中一個(gè)CONSTANT_Utf8類型的項(xiàng),就是一個(gè)CONSTANT_Utf8_info 。除此之外, 每個(gè)info項(xiàng)中都有一個(gè)標(biāo)志值(tag),這個(gè)標(biāo)志值表明了這個(gè)常量池中的info項(xiàng)的類型是什么, 從上面的表格中可以看出,一個(gè)CONSTANT_Utf8_info中的tag值為1,而一個(gè)CONSTANT_Fieldref_info中的tag值為9 。
Java程序是動(dòng)態(tài)鏈接的, 在動(dòng)態(tài)鏈接的實(shí)現(xiàn)中, 常量池扮演者舉足輕重的角色。 除了存放一些字面量之外, 常量池中還存放著以下幾種符號(hào)引用:
(1) 類和接口的全限定名
(2) 字段的名稱和描述符
(3) 方法的名稱和描述符
我們有必要先了解一下class文件中的特殊字符串, 因?yàn)樵诔A砍刂校?特殊字符串大量的出現(xiàn),這些特殊字符串就是上面說(shuō)的全限定名和描述符。對(duì)于常量池中的特殊字符串的了解,可以參考此文檔:Java class文件格式之特殊字符串_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
4、access_flag 保存了當(dāng)前類的訪問(wèn)權(quán)限
5、this_cass 保存了當(dāng)前類的全局限定名在常量池里的索引
6、super class 保存了當(dāng)前類的父類的全局限定名在常量池里的索引
7、interfaces 保存了當(dāng)前類實(shí)現(xiàn)的接口列表,包含兩部分內(nèi)容:interfaces_count 和interfaces[interfaces_count]
interfaces_count 指的是當(dāng)前類實(shí)現(xiàn)的接口數(shù)目
interfaces[] 是包含interfaces_count個(gè)接口的全局限定名的索引的數(shù)組
8、fields 保存了當(dāng)前類的成員列表,包含兩部分的內(nèi)容:fields_count 和 fields[fields_count]
fields_count是類變量和實(shí)例變量的字段的數(shù)量總和。
fileds[]是包含字段詳細(xì)信息的列表。
9、methods 保存了當(dāng)前類的方法列表,包含兩部分的內(nèi)容:methods_count和methods[methods_count]
methods_count是該類或者接口顯示定義的方法的數(shù)量。
method[]是包含方法信息的一個(gè)詳細(xì)列表。
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表里的屬性,那么它就是對(duì)整個(gè)class文件所對(duì)應(yīng)的類或者接口的描述;如果出現(xiàn)在fileds的某一項(xiàng)里,那么它就是對(duì)該字段額外信息的描述;如果出現(xiàn)在methods的某一項(xiàng)里,那么它就是對(duì)該方法額外信息的描述。
通過(guò)示例代碼來(lái)手動(dòng)分析class文件
上面大致講解了一下class文件的結(jié)構(gòu),這里,我們拿一個(gè)class文件做一個(gè)簡(jiǎn)單的分析,來(lái)驗(yàn)證上面的文件結(jié)構(gòu)是否確實(shí)是如此。
我們?cè)谶@里新建一個(gè)java文件,Hello.java,具體內(nèi)容如下:
public class Hello{
private int test;
public int test(){
return test;
}
}
然后再通過(guò)javac命令將此java文件編譯成class文件:
javac /d/class_file_test/Hello.java
編譯之后的class文件十六進(jìn)制結(jié)果如下所示,可以用UltraEdit等十六進(jìn)制編輯器打開(kāi),得到:
接下來(lái)我們就按照class文件的格式來(lái)分析上面的一串?dāng)?shù)字,還是按照之前的順序來(lái):
magic:
CA FE BA BE
,代表該文件是一個(gè)字節(jié)碼文件,我們平時(shí)區(qū)分文件類型都是通過(guò)后綴名來(lái)區(qū)分的,不過(guò)后綴名是可以隨便修改的,所以僅靠后綴名不能真正區(qū)分一個(gè)文件的類型。區(qū)分文件類型的另個(gè)辦法就是magic數(shù)字,JVM 就是通過(guò) CA FE BA BE 來(lái)判斷該文件是不是class文件-
version字段:
00 00 00 34
,前兩個(gè)字節(jié)00是minor_version,后兩個(gè)字節(jié)0034是major_version字段,對(duì)應(yīng)的十進(jìn)制值為52,也就是說(shuō)當(dāng)前class文件的主版本號(hào)為52,次版本號(hào)為0。下表是jdk 1.6 以后對(duì)應(yīng)支持的 Class 文件版本號(hào):image_1c2th3nii1otd13vg1vhg1hl6itg4i.png-23.7kB -
常量池,constant_pool:
3.1.constant_pool_count
緊接著version字段下來(lái)的兩個(gè)字節(jié)是:00 12
代表常量池里包含的常量數(shù)目,因?yàn)樽止?jié)碼的常量池是從1開(kāi)始計(jì)數(shù)的,這個(gè)常量池包含17個(gè)(0x0012-1)常量。3.2.constant_pool
接下來(lái)就是分析這17個(gè)常量:
3.2.1. 第一個(gè)變量 0a 00 04 00 0e
首先,緊接著constant_pool_count的第一個(gè)字節(jié)0a(tag=10)根據(jù)上面的表格(文中第二張圖片)
![image_1c2tj6ib6pslkbb1876ot81rjj4v.png-4kB][7]
可知,這表示的是一個(gè)CONSTANT_Methodref。CONSTANT_Methodref的結(jié)構(gòu)如下:
CONSTANT_Methodref_info {
u1 tag; //u1表示占一個(gè)字節(jié)
u2 class_index; //u2表示占兩個(gè)字節(jié)
u2 name_and_type_index; //u2表示占兩個(gè)字節(jié)
}
其中class_index表示該方法所屬的類在常量池里的索引,name_and_type_index表示該方法的名稱和類型的索引。常量池里的變量的索引從1開(kāi)始。
那么這個(gè)methodref結(jié)構(gòu)的數(shù)據(jù)如下:
0a //tag 10表示這是一個(gè)CONSTANT_Methodref_info結(jié)構(gòu)
00 04 //class_index 指向常量池中第4個(gè)常量所表示的類
00 0e //name_and_type_index 指向常量池中第14個(gè)常量所表示的方法
3.2.2. 第二個(gè)變量09 00 03 00 0F
接著是第二個(gè)常量,它的tag是09,根據(jù)上面的表格可知,這表示的是一個(gè)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個(gè)常量所表示的類
00 0f //指向常量池中第15個(gè)常量所表示的變量
3.2.3. 第三個(gè)變量 07 00 10
tag為07表示是一個(gè)CONSTANT_Class變量,這個(gè)變量的結(jié)構(gòu)如下:
CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
除了tag字段以外,還有一個(gè)name_index的值為00 10
,即是指向常量池中第16個(gè)常量所表示的Class名稱。
3.2.4. 第四個(gè)變量07 00 11
同上,也是一個(gè)CONSTANT_Class變量,不過(guò),指向的是第17個(gè)常量所表示的Class名稱。
3.2.5. 第五個(gè)變量 01 00 04 74 65 73 74
tag為1,表示這是一個(gè)CONSTANT_Utf8結(jié)構(gòu),這種結(jié)構(gòu)用UTF-8的一種變體來(lái)表示字符串,結(jié)構(gòu)如下所示:
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
其中l(wèi)ength表示該字符串的字節(jié)數(shù),bytes字段包含該字符串的二進(jìn)制表示。
01 //tag 1表示這是一個(gè)CONSTANT_Utf8結(jié)構(gòu)
00 04 //表示這個(gè)字符串的長(zhǎng)度是4字節(jié),也就是后面的四個(gè)字節(jié)74 65 73 74
74 65 73 74 //通過(guò)ASCII碼表轉(zhuǎn)換后,表示的是字符串“test”
接下來(lái)的8個(gè)變量都是字符串,這里就不具體分析了。
3.2.6. 第十四個(gè)常量 0c 00 07 00 08
tag為0c,表示這是一個(gè)CONSTANT_NameAndType結(jié)構(gòu),這個(gè)結(jié)構(gòu)用來(lái)描述一個(gè)方法或者成員變量。具體結(jié)構(gòu)如下:
CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
name_index表示的是該變量或者方法的名稱,這里的值是0007,表示指向第7個(gè)常量,即是<init>
。
descriptor_index指向該方法的描述符的引用,這里的值是0008,表示指向第8個(gè)常量,即是()V
,由前面描述符的語(yǔ)法可知,這個(gè)方法是一個(gè)無(wú)參的,返回值為void的方法。
綜合兩個(gè)字段,可以推出這個(gè)方法是void <init>()
。也即是指向這個(gè)NameAndType結(jié)構(gòu)的Methodref的方法名為void <init>()
,也就是說(shuō)第一個(gè)常量表示的是void <init>()
方法,這個(gè)方法其實(shí)就是此類的默認(rèn)構(gòu)造方法。
3.2.7. 第十五個(gè)常量也是一個(gè)CONSTANT_NameAndType,表示的方法名為“int test()”,第2個(gè)常量引用了這個(gè)NameAndType,所以第二個(gè)常量表示的是“int test()”方法。
3.2.8. 第16和17個(gè)常量也是字符串,可以按照前面的方法分析。
3.3. 完整的常量池
最后,通過(guò)以上分析,完整的常量池如下:
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
通過(guò)這樣分析其實(shí)非常的累,我們只是為了了解class文件的原理才來(lái)一步一步分析每一個(gè)二進(jìn)制字節(jié)碼。JDK提供了現(xiàn)成的工具可以直接解析此二進(jìn)制文件,即javap工具(在JDK的bin目錄下),我們通過(guò)javap命令來(lái)解析此class文件:
javap -v -p -s -sysinfo -constants /d/class_file_test/Hello.class
解析得到的結(jié)果為:
關(guān)于此表每一項(xiàng)的詳細(xì)分析,可以參考國(guó)外的這一篇文檔:JVM Internals
關(guān)于此表中Method操作指令aload_1,getfield,ireturn
的作用,可以參考云溪社區(qū)的這篇文章:
JVM Class詳解之二 Method字節(jié)碼指令
發(fā)現(xiàn)了沒(méi)有,上面生成代碼中的Constant pool
跟我們上面分析出來(lái)的完整常量池一模一樣,有木有!有木有?
這說(shuō)明我們上面的分析的完成正確!
由此,我們終于弄懂了Constant pool
的內(nèi)幕。
接下來(lái)繼續(xù)看其他的字段。
-
access_flag(u2)
00 21
這兩個(gè)字節(jié)的數(shù)據(jù)表示這個(gè)變量的訪問(wèn)標(biāo)志位,JVM對(duì)訪問(wèn)標(biāo)示符的規(guī)范如下:
這個(gè)表里面無(wú)法直接查詢到0021這個(gè)值,原因是0021=0020+0001,也就是表示當(dāng)前class的access_flag是ACC_PUBLIC|ACC_SUPER
。ACC_PUBLIC和代碼里的public 關(guān)鍵字相對(duì)應(yīng)。ACC_SUPER表示當(dāng)用invokespecial指令來(lái)調(diào)用父類的方法時(shí)需要特殊處理。
this_class(u2)
00 03
this_class指向constant pool的索引值,該值必須是CONSTANT_Class_info類型,這里是3,即指向常量池中的第三項(xiàng),即是“Hello”。super_class
00 04
super_class存的是父類的名稱在常量池里的索引,這里指向第四個(gè)常量,即是“java/lang/Object”。interfaces
interfaces包含interfaces_count和interfaces[]兩個(gè)字段。因?yàn)檫@里沒(méi)有實(shí)現(xiàn)接口,所以就不存在interfces選項(xiàng),所以這里的interfaces_count為0(0000),所以后面的內(nèi)容也對(duì)應(yīng)為空。fields
00 01 fields count //表示成員變量的個(gè)數(shù),此處為1個(gè)
00 02 00 05 00 06 00 00 //成員變量的結(jié)構(gòu)
每個(gè)成員變量對(duì)應(yīng)一個(gè)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指向常量池的第五個(gè)常量,為“test”
descriptor_index指向常量池的第6個(gè)常量為“I”
三個(gè)字段結(jié)合起來(lái),說(shuō)明這個(gè)變量是"private int test"。
接下來(lái)的是attribute字段,用來(lái)描述該變量的屬性,因?yàn)檫@個(gè)變量沒(méi)有附加屬性,所以attributes_count為0,attribute_info為空。
-
methods
00 02 00 01 00 07 00 08 00 01 00 09 ...
最前面的2個(gè)字節(jié)是method_count
method_count:00 02
,為什么會(huì)有兩個(gè)方法呢?我們明明只寫(xiě)了一個(gè)方法,這是因?yàn)镴VM 會(huì)自動(dòng)生成一個(gè)<init>
方法,這個(gè)是類的默認(rèn)構(gòu)造方法。
接下來(lái)的內(nèi)容是兩個(gè)method_info
結(jié)構(gòu):
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
前三個(gè)字段和field_info一樣,可以分析出第一個(gè)方法是“public void <init>()”
00 01 ACC_PUBLIC
00 07 <init>
00 08 V()
接下來(lái)是attribute字段,也即是這個(gè)方法的附加屬性,這里的attributes_count =1,也即是有一個(gè)屬性。
每個(gè)屬性的都是一個(gè)attribute_info結(jié)構(gòu),如下所示:
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
JVM預(yù)定義了部分attribute,但是編譯器自己也可以實(shí)現(xiàn)自己的attribute寫(xiě)入class文件里,供運(yùn)行時(shí)使用。不同的attribute通過(guò)attribute_name_index來(lái)區(qū)分。JVM規(guī)范里對(duì)以下attribute進(jìn)行了預(yù)定義:
這里的attribute_name_index值為0009,表示指向第9個(gè)常量,即是Code。Code Attribute的作用是保存該方法的結(jié)構(gòu)如所對(duì)應(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個(gè)字節(jié),不包含attribute_name_index和attribute_length字段。
max_stack表示這個(gè)方法運(yùn)行的任何時(shí)刻所能達(dá)到的操作數(shù)棧的最大深度,這里是0001
max_locals表示方法執(zhí)行期間創(chuàng)建的局部變量的數(shù)目,包含用來(lái)表示傳入的參數(shù)的局部變量,這里是0001.
接下來(lái)的code_length表示該方法的所包含的字節(jié)碼的字節(jié)數(shù)以及具體的指令碼。
這里的字節(jié)碼長(zhǎng)度為00000005,即是后面的5個(gè)字節(jié) 2a b7 00 01 b1為對(duì)應(yīng)的字節(jié)碼指令的指令碼。
參照下表可以將上面的指令碼翻譯成對(duì)應(yīng)的助記符:
2a aload_0
b7 invokespecial
00 nop
01 aconst_null
b1 return
這即是該方法被調(diào)用時(shí),虛擬機(jī)所執(zhí)行的字節(jié)碼
接下來(lái)是exception_table,這里存放的是處理異常的信息。
每個(gè)exception_table表項(xiàng)由start_pc,end_pc,handler_pc,catch_type組成。start_pc和end_pc表示在code數(shù)組中的從start_pc到end_pc處(包含start_pc,不包含end_pc)的指令拋出的異常會(huì)由這個(gè)表項(xiàng)來(lái)處理;handler_pc表示處理異常的代碼的開(kāi)始處。catch_type表示會(huì)被處理的異常類型,它指向常量池里的一個(gè)異常類。當(dāng)catch_type為0時(shí),表示處理所有的異常,這個(gè)可以用來(lái)實(shí)現(xiàn)finally的功能。
不過(guò),這段代碼里沒(méi)有異常處理,所以exception_table_length為0000,所以我們不做分析。
接下來(lái)是該方法的附加屬性,attributes_count為0001,表示有一個(gè)附加屬性。
attribute_name_index為000a,指向第十個(gè)常量,為L(zhǎng)ineNumberTable。這個(gè)屬性用來(lái)表示code數(shù)組中的字節(jié)碼和java代碼行數(shù)之間的關(guān)系。這個(gè)屬性可以用來(lái)在調(diào)試的時(shí)候定位代碼執(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];
}
前面兩個(gè)字段分別表示這個(gè)attribute的名稱是LineNumberTable以及長(zhǎng)度為00000006。接下來(lái)的0001表示line_number_table_length,表示line_number_table有一個(gè)表項(xiàng),其中start_pc為 00 00,line_number為 00 00,表示第0行代碼從code的第0個(gè)指令碼開(kāi)始。
后面的內(nèi)容是第二個(gè)方法,具體就不再分析了。
- attributes
最后剩下的內(nèi)容是attributes,這里的attributes表示整個(gè)class文件的附加屬性,不過(guò)結(jié)構(gòu)還是和前面的attribute保持一致。00 01
表示有一個(gè)attribute。
Attribute結(jié)構(gòu)如下:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
attribute_name_index為000c,指向第12個(gè)常量,為SourceFile,說(shuō)明這個(gè)屬性是Source
attribute_length為00000002
sourcefile_index為000d,表示指向常量池里的第13個(gè)常量,為Hello.java
。
這個(gè)屬性表明當(dāng)前的class文件是從Hello.java文件編譯而來(lái)。
字節(jié)碼修改技術(shù)
對(duì)Java Class字節(jié)碼分析,我們應(yīng)該能夠比較清楚的認(rèn)識(shí)到整個(gè)字節(jié)碼的結(jié)構(gòu)。
那通過(guò)了解字節(jié)碼,我們可以做些什么呢?
其實(shí)通過(guò)字節(jié)碼能做很多平時(shí)我們無(wú)法完成的工作。比如,在類加載之前添加某些操作或者直接動(dòng)態(tài)的生成字節(jié)。
ASM 是一個(gè) Java 字節(jié)碼操控框架。它能夠以二進(jìn)制形式修改已有類或者動(dòng)態(tài)生成類。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件,也可以在類被加載入 Java 虛擬機(jī)之前動(dòng)態(tài)改變類行為。ASM 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據(jù)用戶要求生成新類。不過(guò)ASM在創(chuàng)建class字節(jié)碼的過(guò)程中,操縱的級(jí)別是底層JVM的匯編指令級(jí)別,這要求ASM使用者要對(duì)class組織結(jié)構(gòu)和JVM匯編指令有一定的了解。
目前字節(jié)碼修改技術(shù)有ASM,javassist,cglib,BCEL等。cglib就是基于封裝的Asm. Spring 就是使用cglib代理庫(kù)。關(guān)于cglib的使用介紹,可以參考:CGLIB介紹與原理
Javassist是一個(gè)開(kāi)源的分析、編輯和創(chuàng)建Java字節(jié)碼的類庫(kù)。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計(jì)算機(jī)科學(xué)系的 Shigeru Chiba (千葉 滋)所創(chuàng)建的。它已加入了開(kāi)放源代碼JBoss 應(yīng)用服務(wù)器項(xiàng)目,通過(guò)使用Javassist對(duì)字節(jié)碼操作為JBoss實(shí)現(xiàn)動(dòng)態(tài)AOP框架。javassist是jboss的一個(gè)子項(xiàng)目,其主要的優(yōu)點(diǎn),在于簡(jiǎn)單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機(jī)指令,就能動(dòng)態(tài)改變類的結(jié)構(gòu),或者動(dòng)態(tài)生成類。