中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

Java字節(jié)碼結(jié)構(gòu)剖析一:常量池

2018-11-20    來(lái)源:importnew

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

這篇博客開(kāi)始,我打算帶大家去解讀一下JVM平臺(tái)下的字節(jié)碼文件(熟悉而又陌生的感覺(jué))。眾所周知,Class文件包含了我們定義的類(lèi)或接口的信息。然后字節(jié)碼又會(huì)被JVM加載到內(nèi)存中,供JVM使用。那么,類(lèi)信息到了字節(jié)碼文件里,它們?nèi)绾伪硎镜,以及在字?jié)碼里是怎么分布的呢?帶著這些問(wèn)題,讓我們?nèi)ド钊肓私庾止?jié)碼文件吧。

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

Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊地排列在Class文件之中,中間沒(méi)有添加任何分隔符,這使得整個(gè)Class文件中存儲(chǔ)的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù),沒(méi)有空隙存在。當(dāng)遇到需要占用8位字節(jié)以上空間地?cái)?shù)據(jù)項(xiàng)時(shí),則會(huì)按照高位在前的方式分割成若干個(gè)8位字節(jié)進(jìn)行存儲(chǔ)。

每一個(gè) Class 文件對(duì)應(yīng)于一個(gè)如下所示的 ClassFile 結(jié)構(gòu)體。

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

這種數(shù)據(jù)結(jié)構(gòu),類(lèi)似C語(yǔ)言結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體中只有兩種數(shù)據(jù)類(lèi)型:無(wú)符號(hào)數(shù)和表,后面的解析都要以這兩種數(shù)據(jù)類(lèi)型為基礎(chǔ),所以這里要先介紹這兩個(gè)概念。

無(wú)符號(hào)數(shù)屬于基本的數(shù)據(jù)類(lèi)型,以u(píng)1,u2,u4,u8來(lái)分別代表1個(gè)字節(jié),2個(gè)字節(jié),4個(gè)字節(jié)和8個(gè)字節(jié)的無(wú)符號(hào)數(shù),無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。

是由多個(gè)無(wú)符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類(lèi)型,所有表都習(xí)慣性地以“_info”結(jié)尾。表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個(gè)Class文件本質(zhì)就是一張表。

下面是我的案例代碼,本章將以此代碼生成的字節(jié)碼文件作為例子來(lái)分析。

public class MyTest2 {
    String str = "Welcome";
    private int x = 5;
    public static Integer in = 10;

    public static void main(String[] args) {
        MyTest2 myTest2 = new MyTest2();
        myTest2.setX(8);
        in = 20;
    }

    public void setX(int x) {
        this.x = x;
    }
}

對(duì)應(yīng)生成的字節(jié)碼文件格式如下:(數(shù)據(jù)內(nèi)容較多,只是截了部分)

上面的數(shù)字是以16進(jìn)制表示的。我們可以按照之前的結(jié)構(gòu)一項(xiàng)項(xiàng)去解讀它。

Class文件解析

magic

魔數(shù),u4類(lèi)型的數(shù)據(jù),占4個(gè)字節(jié)。魔數(shù)的唯一作用是確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)所接受的 Class 文件。魔數(shù)值固定為 0xCAFEBABE(咖啡寶貝),不會(huì)改變。

minor_version、major_version

緊接著魔數(shù)之后的4個(gè)字節(jié)為Java版本信息:第5和第6個(gè)字節(jié)是次版本號(hào)(minor_version),第7和第8個(gè)字節(jié)是主版本號(hào)(major_version)。

就看當(dāng)前這個(gè)字節(jié)碼,次版本號(hào)是0×0000=0,主版本號(hào)是0×0034=52。我本地機(jī)器用的是JDK1.8,所以可生成的Class文件主版本號(hào)最大值為52.0。

下面給出了Java各個(gè)主版本號(hào),以供參考。

constant_pool_count

常量池計(jì)數(shù)器,u2類(lèi)型的數(shù)據(jù)。它是常量池的入口,表示緊跟著它后面的常量池的元素個(gè)數(shù)。算一下,0x002F=47,即常量池里的元素有47個(gè)。這里我用jdk的內(nèi)置工具javap,反編譯一下,可以輸出常量池的信息以及元素個(gè)數(shù)。執(zhí)行命令:javap -verbose。輸出結(jié)果如下:

Constant pool:
   #1 = Methodref          #10.#34        // java/lang/Object."<init>":()V
   #2 = String             #35            // Welcome
   #3 = Fieldref           #5.#36         // com/shengsiyuan/jvm/bytecode/MyTest2.str:Ljava/lang/String;
   #4 = Fieldref           #5.#37         // com/shengsiyuan/jvm/bytecode/MyTest2.x:I
   #5 = Class              #38            // com/shengsiyuan/jvm/bytecode/MyTest2
   #6 = Methodref          #5.#34         // com/shengsiyuan/jvm/bytecode/MyTest2."<init>":()V
   #7 = Methodref          #5.#39         // com/shengsiyuan/jvm/bytecode/MyTest2.setX:(I)V
   #8 = Methodref          #40.#41        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #9 = Fieldref           #5.#42         // com/shengsiyuan/jvm/bytecode/MyTest2.in:Ljava/lang/Integer;
  #10 = Class              #43            // java/lang/Object
  #11 = Utf8               str
  #12 = Utf8               Ljava/lang/String;
  #13 = Utf8               x
  #14 = Utf8               I
  #15 = Utf8               in
  #16 = Utf8               Ljava/lang/Integer;
  #17 = Utf8               <init>
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               this
  #23 = Utf8               Lcom/shengsiyuan/jvm/bytecode/MyTest2;
  #24 = Utf8               main
  #25 = Utf8               ([Ljava/lang/String;)V
  #26 = Utf8               args
  #27 = Utf8               [Ljava/lang/String;
  #28 = Utf8               myTest2
  #29 = Utf8               setX
  #30 = Utf8               (I)V
  #31 = Utf8               <clinit>
  #32 = Utf8               SourceFile
  #33 = Utf8               MyTest2.java
  #34 = NameAndType        #17:#18        // "<init>":()V
  #35 = Utf8               Welcome
  #36 = NameAndType        #11:#12        // str:Ljava/lang/String;
  #37 = NameAndType        #13:#14        // x:I
  #38 = Utf8               com/shengsiyuan/jvm/bytecode/MyTest2
  #39 = NameAndType        #29:#30        // setX:(I)V
  #40 = Class              #44            // java/lang/Integer
  #41 = NameAndType        #45:#46        // valueOf:(I)Ljava/lang/Integer;
  #42 = NameAndType        #15:#16        // in:Ljava/lang/Integer;
  #43 = Utf8               java/lang/Object
  #44 = Utf8               java/lang/Integer
  #45 = Utf8               valueOf
  #46 = Utf8               (I)Ljava/lang/Integer;

可是,我們得到的常量池里的元素個(gè)數(shù)是46。我們看常量池第一個(gè)元素,它的索引是從1開(kāi)始的。所以索引值范圍是1~46。設(shè)計(jì)者將第0項(xiàng)常量空出來(lái)是有特殊考慮的,這樣做的目的在于滿(mǎn)足后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下需要表達(dá)“不引用任何一個(gè)常量池項(xiàng)目”的含義,這種情況就可以把索引值置為0來(lái)表示。根本原因在于,索引為0也是一個(gè)常量(保留常量),只不過(guò)它不位于常量表中。這個(gè)常量就對(duì)應(yīng)Null值,所以常量池的索引從1而非0開(kāi)始。

常量池結(jié)構(gòu)剖析

緊接其后的就是常量池了。一個(gè)Java類(lèi)中定義的很多信息都是由常量池維護(hù)和描述的。可以將常量池看作是Class文件的資源庫(kù)。比如:Java類(lèi)中定義的方法與變量信息,都是存儲(chǔ)在常量池中。常量池中主要存儲(chǔ)兩類(lèi)常量:字面常量和符號(hào)引用。字面量,如文本字符串,Java中聲明為常量值,而符號(hào)引用如類(lèi)和接口的全局限定名,字段的名稱(chēng)和描述符,方法的名稱(chēng)和描述符等。

注:常量池中存儲(chǔ)的不一定是不變的量!如,private int x = 5,x是變量,但“x”這個(gè)變量名字依然存在常量池中。

我們也可以把常量池當(dāng)做一個(gè)數(shù)組(常量池中的每一項(xiàng)常量都是一個(gè)表),與一般數(shù)組不同的是,常量池?cái)?shù)組中不同的元素類(lèi)型,結(jié)構(gòu)都是不同的,長(zhǎng)度當(dāng)然也不相同;但是每一個(gè)元素的第一個(gè)數(shù)據(jù)都是u1類(lèi)型,該字節(jié)是個(gè)標(biāo)志位,占一個(gè)字節(jié)。JVM在解析長(zhǎng)量池時(shí),會(huì)根據(jù)這個(gè)u1類(lèi)型來(lái)獲取元素的具體類(lèi)型。目前,常量池中出現(xiàn)的常量類(lèi)型有14種,如下表:

有了這張表就可以繼續(xù)剖析常量池的內(nèi)容了,常量池第一個(gè)字節(jié)就是一個(gè)標(biāo)志位,0x000A=10,說(shuō)明第一個(gè)常量類(lèi)型是CONSTANT_Methodref_info。這是一個(gè)表類(lèi)型,它對(duì)應(yīng)的結(jié)構(gòu)是:

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

可知,該類(lèi)型常量占1+2+2=5個(gè)字節(jié)。所以我們從常量池前5個(gè)字節(jié)就是第一個(gè)常量元素了。緊接后面就是第二個(gè)常量,同樣的,開(kāi)始是一個(gè)標(biāo)志位,即0x008=8。可知,第二個(gè)常量是CONSTANT_String_info類(lèi)型。CONSTANT_String_info 用于表示java.lang.String類(lèi)型的常量對(duì)象,格式如下:

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}

所以常量池的第二個(gè)元素占3個(gè)字節(jié)。按照這個(gè)套路,我們就可以找出每一個(gè)常量了。一直數(shù)到第46個(gè)常量,常量池就結(jié)束了。此處是常量池中的14種常量項(xiàng)的結(jié)構(gòu)總表。感興趣的可以對(duì)照這個(gè)表,去把剩下的常量對(duì)照出來(lái)。

常量項(xiàng)分析

第一個(gè)常量是CONSTANT_Methodref_info類(lèi)型的,它描述了類(lèi)中方法的符號(hào)引用。class_index 項(xiàng)的值必須是對(duì)常量池的有效索引,常量池在該索引處的項(xiàng)必須是CONSTANT_Class_info結(jié)構(gòu),表示一個(gè)類(lèi)或接口。

class_index表示的索引值是0x000A=10。根據(jù)之前?javap -verbose?輸出的常量池信息,我們可以知道常量池的#10項(xiàng)是CONSTANT_Class_info類(lèi)型的常量。該類(lèi)型常量用于表示類(lèi)或接口,格式如下:

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

name_index 項(xiàng)的值,必須是對(duì)常量池的一個(gè)有效索引。常量池在該索引處的項(xiàng)必須是CONSTANT_Utf8_info結(jié)構(gòu),代表一個(gè)有效的類(lèi)或接口二進(jìn)制名稱(chēng)的內(nèi)部形式。

name_index 表示的索引值是43(這里我直接從上面的量池信息讀出,如果從字節(jié)碼里看,此處的值為0x002B=43)。所以接著找常量池第43項(xiàng)的常量類(lèi)型,是CONSTANT_utf8_info類(lèi)型,用于表示字符串常量的值,結(jié)構(gòu)如下:

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

其中,length 項(xiàng)的值指明了 bytes[]數(shù)組的長(zhǎng)度,bytes[]是表示字符串值的byte數(shù)組。在這里,我把字節(jié)碼常量池中#43處常量的16進(jìn)制值單獨(dú)拿出來(lái)來(lái)看。下圖有背景色的部分就是完整的CONSTANT_Utf8_info類(lèi)型常量表示。

第一個(gè)字節(jié)是標(biāo)志位,0×0001=1。說(shuō)明此常量類(lèi)型是CONSTANT_Utf8_info。后面2個(gè)字節(jié)是0×0010=16,表示后面bytes[]長(zhǎng)度為16。所以往后數(shù)16個(gè)字節(jié)就是整個(gè)它表示的字符串常量。

bytes[]第一個(gè)字節(jié)值,0x006A。根據(jù) ASCII碼對(duì)照表,代表的字符串是”j”。依次的,第二個(gè)字節(jié)0×0061,代表“a”,等等。把16個(gè)字節(jié)看完你就得到了字符串常量表示“java/lang/Object”。好了這表示一個(gè)類(lèi)的全限定名。饒了一大圈,終于找到最終要表示的常量信息了。

到此,我們把第一個(gè)常量的結(jié)構(gòu)中的class_index就解析完了,還剩一個(gè)name_and_type_index。它表示了常量池在該索引處的項(xiàng)必須是 CONSTANT_NameAndType_info結(jié)構(gòu),它表示當(dāng)前字段或方法的名字和描述符。后面大家可以根據(jù)常量池中的14種常量項(xiàng)的結(jié)構(gòu)總表,并結(jié)合javap得到的常量池信息,自己去分析每個(gè)常量在常量池里是怎么個(gè)回事。

總結(jié)

這篇文章介紹了,字節(jié)碼文件的結(jié)構(gòu)組成,并分析了魔數(shù)、次主版本號(hào)和常量池。尤其帶大家深入分析了常量池的組成結(jié)構(gòu),并拿例子中的常量池第一個(gè)常量作為案例,完整解析它在常量池中的各項(xiàng)引用。套路都是一樣的,常量池后面的常量,大家可以自己去分析了。你會(huì)發(fā)現(xiàn)類(lèi)中有用的信息都存在了我們的常量池里,然后以索引的形式,給代碼使用。這也就是常量池作為class文件的資源倉(cāng)庫(kù)的原因了。

標(biāo)簽: 代碼

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:Ubuntu上更改MySQL數(shù)據(jù)庫(kù)數(shù)據(jù)存儲(chǔ)目錄

下一篇:FutureTask在線程池中應(yīng)用和源碼解析