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

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

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

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

ClassFile{u4magic;u2minor_version;u2major_version;u2constant_pool_count;cp_infoconstant_pool[constant_pool_count-1];u2access_flags;u2this_class;u2super_class;u2interfaces_count;u2interfaces[interfaces_count];u2fields_count;field_infofields[fields_count];u2methods_count;method_infomethods[methods_count];u2attributes_count;attribute_infoattributes[attributes_count];}

這種數(shù)據(jù)結(jié)構(gòu)叨吮,類似C語言結(jié)構(gòu)體。這個結(jié)構(gòu)體中只有兩種數(shù)據(jù)類型:無符號數(shù)和表瞬矩,后面的解析都要以這兩種數(shù)據(jù)類型為基礎(chǔ)挤安,所以這里要先介紹這兩個概念。

無符號數(shù)屬于基本的數(shù)據(jù)類型丧鸯,以u1蛤铜,u2,u4,u8來分別代表1個字節(jié)围肥,2個字節(jié)剿干,4個字節(jié)和8個字節(jié)的無符號數(shù),無符號數(shù)可以用來描述數(shù)字穆刻、索引引用置尔、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。

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

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

publicclassMyTest2{? ? String str ="Welcome";privateintx =5;publicstaticIntegerin=10;publicstaticvoidmain(String[] args){? ? ? ? MyTest2 myTest2 =newMyTest2();? ? ? ? myTest2.setX(8);in=20;? ? }publicvoidsetX(intx){this.x = x;? ? }}

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

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

Class文件解析

magic

魔數(shù)诬烹,u4類型的數(shù)據(jù)砸烦,占4個字節(jié)。魔數(shù)的唯一作用是確定這個文件是否為一個能被虛擬機所接受的 Class 文件绞吁。魔數(shù)值固定為?0xCAFEBABE?(咖啡寶貝)幢痘,不會改變。

minor_version家破、major_version

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

就看當(dāng)前這個字節(jié)碼员舵,次版本號是0×0000=0脑沿,主版本號是0×0034=52藕畔。我本地機器用的是JDK1.8马僻,所以可生成的Class文件主版本號最大值為52.0。

下面給出了Java各個主版本號注服,以供參考韭邓。

constant_pool_count

常量池計數(shù)器,u2類型的數(shù)據(jù)溶弟。它是常量池的入口女淑,表示緊跟著它后面的常量池的元素個數(shù)。算一下辜御,0x002F=47鸭你,即常量池里的元素有47個。這里我用jdk的內(nèi)置工具javap,反編譯一下袱巨,可以輸出常量池的信息以及元素個數(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= Utf8in#16= Utf8? ? ? ? ? ? ? Ljava/lang/Integer;#17= Utf8? ? ? ? ? ? ? #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? ? ? ? ? ? ? #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;

可是愉老,我們得到的常量池里的元素個數(shù)是46场绿。我們看常量池第一個元素,它的索引是從1開始的嫉入。所以索引值范圍是1~46焰盗。設(shè)計者將第0項常量空出來是有特殊考慮的,這樣做的目的在于滿足后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下需要表達“不引用任何一個常量池項目”的含義咒林,這種情況就可以把索引值置為0來表示熬拒。根本原因在于,索引為0也是一個常量(保留常量)映九,只不過它不位于常量表中梦湘。這個常量就對應(yīng)Null值,所以常量池的索引從1而非0開始件甥。

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

緊接其后的就是常量池了捌议。一個Java類中定義的很多信息都是由常量池維護和描述的∫校可以將常量池看作是Class文件的資源庫瓣颅。比如:Java類中定義的方法與變量信息,都是存儲在常量池中譬正。常量池中主要存儲兩類常量:字面常量和符號引用宫补。字面量,如文本字符串曾我,Java中聲明為常量值粉怕,而符號引用如類和接口的全局限定名,字段的名稱和描述符抒巢,方法的名稱和描述符等贫贝。

注:常量池中存儲的不一定是不變的量!如蛉谜,?private int x = 5?稚晚,x是變量,但“x”這個變量名字依然存在常量池中型诚。

我們也可以把常量池當(dāng)做一個數(shù)組(常量池中的每一項常量都是一個表)客燕,與一般數(shù)組不同的是,常量池數(shù)組中不同的元素類型狰贯,結(jié)構(gòu)都是不同的也搓,長度當(dāng)然也不相同赏廓;但是每一個元素的第一個數(shù)據(jù)都是u1類型,該字節(jié)是個標(biāo)志位傍妒,占一個字節(jié)楚昭。JVM在解析長量池時,會根據(jù)這個u1類型來獲取元素的具體類型拍顷。目前抚太,常量池中出現(xiàn)的常量類型有14種,如下表:

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

CONSTANT_Methodref_info{u1tag;u2class_index;u2name_and_type_index;}

可知,該類型常量占1+2+2=5個字節(jié)捞稿。所以我們從常量池前5個字節(jié)就是第一個常量元素了又谋。緊接后面就是第二個常量,同樣的娱局,開始是一個標(biāo)志位彰亥,即0x008=8∷テ耄可知任斋,第二個常量是CONSTANT_String_info類型。CONSTANT_String_info 用于表示?java.lang.String?類型的常量對象耻涛,格式如下:

CONSTANT_String_info{u1tag;u2string_index;}

所以常量池的第二個元素占3個字節(jié)废酷。按照這個套路,我們就可以找出每一個常量了抹缕。一直數(shù)到第46個常量澈蟆,常量池就結(jié)束了。此處是常量池中的?14種常量項的結(jié)構(gòu)總表?卓研。感興趣的可以對照這個表趴俘,去把剩下的常量對照出來。

常量項分析

第一個常量是CONSTANT_Methodref_info類型的鉴分,它描述了類中方法的符號引用哮幢。class_index 項的值必須是對常量池的有效索引带膀,常量池在該索引處的項必須是CONSTANT_Class_info結(jié)構(gòu)志珍,表示一個類或接口。

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

CONSTANT_Class_info{u1tag;u2name_index;}

name_index 項的值敛纲,必須是對常量池的一個有效索引喂击。常量池在該索引處的項必須是CONSTANT_Utf8_info結(jié)構(gòu),代表一個有效的類或接口二進制名稱的內(nèi)部形式淤翔。

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

CONSTANT_Utf8_info{u1tag;u2length;u1bytes[length];}

其中裁奇,length 項的值指明了 bytes[]數(shù)組的長度,bytes[]是表示字符串值的byte數(shù)組麦撵。在這里刽肠,我把字節(jié)碼常量池中#43處常量的16進制值單獨拿出來來看。下圖有背景色的部分就是完整的CONSTANT_Utf8_info類型常量表示免胃。

第一個字節(jié)是標(biāo)志位音五,0×0001=1。說明此常量類型是CONSTANT_Utf8_info羔沙。后面2個字節(jié)是0×0010=16放仗,表示后面bytes[]長度為16。所以往后數(shù)16個字節(jié)就是整個它表示的字符串常量撬碟。

bytes[]第一個字節(jié)值诞挨,0x006A。根據(jù)?ASCII碼對照表?呢蛤,代表的字符串是”j”惶傻。依次的,第二個字節(jié)0×0061其障,代表“a”银室,等等。把16個字節(jié)看完你就得到了字符串常量表示“java/lang/Object”励翼。好了這表示一個類的全限定名蜈敢。饒了一大圈,終于找到最終要表示的常量信息了汽抚。

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

總結(jié)

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

 在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流群贩虾。交流學(xué)習(xí)群號:938837867 暗號:555 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis沥阱,Netty源碼分析缎罢,高并發(fā)、高性能考杉、分布式策精、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化崇棠、分布式架構(gòu)等這些成為架構(gòu)師必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咽袜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子枕稀,更是在濱河造成了極大的恐慌询刹,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萎坷,死亡現(xiàn)場離奇詭異凹联,居然都是意外死亡,警方通過查閱死者的電腦和手機哆档,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門蔽挠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虐呻,你說我怎么就攤上這事象泵。” “怎么了斟叼?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵偶惠,是天一觀的道長。 經(jīng)常有香客問我朗涩,道長忽孽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任谢床,我火速辦了婚禮兄一,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘识腿。我一直安慰自己出革,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布渡讼。 她就那樣靜靜地躺著骂束,像睡著了一般。 火紅的嫁衣襯著肌膚如雪成箫。 梳的紋絲不亂的頭發(fā)上展箱,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音蹬昌,去河邊找鬼混驰。 笑死,一個胖子當(dāng)著我的面吹牛皂贩,可吹牛的內(nèi)容都是我干的栖榨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼明刷,長吁一口氣:“原來是場噩夢啊……” “哼治泥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起遮精,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤居夹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后本冲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體准脂,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年檬洞,在試婚紗的時候發(fā)現(xiàn)自己被綠了狸膏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡添怔,死狀恐怖湾戳,靈堂內(nèi)的尸體忽然破棺而出贤旷,到底是詐尸還是另有隱情,我是刑警寧澤砾脑,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布幼驶,位于F島的核電站,受9級特大地震影響韧衣,放射性物質(zhì)發(fā)生泄漏盅藻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一畅铭、第九天 我趴在偏房一處隱蔽的房頂上張望氏淑。 院中可真熱鬧,春花似錦硕噩、人聲如沸假残。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽守问。三九已至,卻和暖如春坑资,著一層夾襖步出監(jiān)牢的瞬間耗帕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工袱贮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仿便,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓攒巍,卻偏偏與公主長得像嗽仪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子柒莉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

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