13. 字節(jié)碼文件解析

魔數(shù)

魔數(shù):所有的.class字節(jié)碼文件的前4個(gè)字節(jié)都是魔數(shù)到踏,魔數(shù)值為固定值:0xCAFEBABE

版本信息

魔數(shù)之后的4個(gè)字節(jié)為版本信息遂填,前2個(gè)字節(jié)是次版本號(hào)(minor version),接下來(lái)2個(gè)字節(jié)是主版本號(hào)(major version)逼蒙。主版本52對(duì)應(yīng)的是java8(1.8)从绘。minor version是0,major version是52是牢,所以僵井,該文件的版本號(hào)為1.8.0〔道猓可以通過(guò)java -version命令來(lái)驗(yàn)證這一點(diǎn)批什。

常量及常量池

常量池(constant pool):緊接著主版本號(hào)之后的就是常量池入口。一個(gè)Java類中定義的很多信息都是由常量池來(lái)維護(hù)和描述的社搅,可以將常量池看作是class文件的資源倉(cāng)庫(kù)驻债,比如說(shuō)Java類中定義的方法與變量信息,都是存儲(chǔ)在常量池中形葬。常量池中主要存儲(chǔ)兩類常量:字面量與符號(hào)引用合呐。字面量如文本字符串,Java中聲明為final的常量值等笙以,而符號(hào)引用如類和接口的全局限定名淌实,字段的名稱和描述符,方法的名稱和描述符等猖腕。

常量池的總體結(jié)構(gòu):Java類所對(duì)應(yīng)的常量池主要由常量池?cái)?shù)量與常量池?cái)?shù)組(常量表)這兩部分構(gòu)成拆祈。常量池?cái)?shù)量緊跟在主版本號(hào)后面,占據(jù)2個(gè)字節(jié)谈息;常量池?cái)?shù)組則緊跟在常量池?cái)?shù)量之后缘屹。常量池?cái)?shù)組與一般的數(shù)組不同的是,常量池?cái)?shù)組中不同的元素的類型侠仇、結(jié)構(gòu)都是不同的轻姿,長(zhǎng)度當(dāng)然也就不同犁珠;但是,每一種元素的第一個(gè)數(shù)據(jù)都是一個(gè)u1類型互亮,該字節(jié)是個(gè)標(biāo)志位犁享,占據(jù)1個(gè)字節(jié)。JVM在解析常量池時(shí)豹休,會(huì)根據(jù)這個(gè)u1類型在獲取元素的具體類型炊昆。值得注意的是,常量池?cái)?shù)組中元素的個(gè)數(shù) = 常量池?cái)?shù) - 1(其中0暫時(shí)不使用)威根,目的是滿足某些常量池索引值的數(shù)據(jù)在特定情況下需要表達(dá)不引用任何一個(gè)常量池的含義凤巨;根本原因在于,索引為0也是一個(gè)常量(保留常量)洛搀,只不過(guò)它不位于常量表中敢茁,這個(gè)常量就對(duì)應(yīng)null值;所以留美,常量池的索引從1而非0開始

class文件結(jié)構(gòu)11中數(shù)據(jù)類型的結(jié)構(gòu)總表


004. class文件結(jié)構(gòu)11中數(shù)據(jù)類型的結(jié)構(gòu)總表.png

上面的表中描述了11種數(shù)據(jù)類型的結(jié)構(gòu)彰檬,其實(shí)在jdk1.7之后又增加了3中(CONSTANT_MethodHandle_info,CONSTANT_MethodHandle_info,CONSTANT_MethodType_info以及CONSTANT_InvokeDynamic_info)。這樣一種是14種

在JVM規(guī)范中谎砾,每個(gè)變量/字段都有描述信息逢倍,描述信息主要的作用是描述字段的數(shù)據(jù)類型、方法的參數(shù)列表(包括數(shù)量景图、類型與順序)與返回值较雕。根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型和代表無(wú)返回值的void類型都用一個(gè)大寫字符來(lái)表示挚币,對(duì)象類型則使用字符L加對(duì)象的全限定名稱來(lái)標(biāo)書(/分割而非.)郎笆。為了壓縮字節(jié)碼文件的體積,對(duì)于基本數(shù)據(jù)類型忘晤,JVM都只用一個(gè)大寫字母來(lái)表示,如下所示:B - byte激捏,C - char设塔,D - double,F(xiàn) - float远舅,I -int闰蛔,J - long,S - short图柏,Z - boolean序六,V - void, L - 對(duì)象類型(字符串類型:Ljava/lang/String;)

對(duì)于數(shù)組類型來(lái)說(shuō)蚤吹,每一個(gè)維度使用一個(gè)前置的[來(lái)表示例诀,如int[]被記錄為[I随抠,String[][]被記錄為[[Ljava/lang/String;

用描述符描述方法時(shí),按照先參數(shù)列表繁涂,后返回值的順序來(lái)描述拱她。參數(shù)列表按照參數(shù)的嚴(yán)格順序放在一組()之內(nèi),如方法:String getRealnameByIdAndNickname(int id, String name)的描述符為:(I, Ljava/lang/String;)Ljava/lang/String;

訪問(wèn)標(biāo)志

access_flag訪問(wèn)標(biāo)志信息包括該Class文件是類還是接口扔罪,是否被定義成public秉沼,是否是abstract,如果是類矿酵,是否被聲明成final

標(biāo)記名 含義
ACC_PUBLIC 0x0001 可以被包的類外訪問(wèn)
ACC_FINAL 0x0010 不允許有子類
ACC_SUPER 0x0020 使用新的invokespecial語(yǔ)義
ACC_INTERFACE 0x0200 標(biāo)識(shí)定義的是接口而不是類
ACC_ABSTRACT 0x0400 不能被實(shí)例化
ACC_SYNTHETIC 0x1000 標(biāo)識(shí)并非Java源碼生成的代碼
ACC_ANNOTATION 0x2000 標(biāo)識(shí)注解類型
ACC_ENUM 0x4000 標(biāo)識(shí)枚舉類型

本例子中access_flag是0x0021唬复,是0x0020與0x0001的并集

當(dāng)前類名、父類名全肮、接口

當(dāng)前類名敞咧,占兩個(gè)字節(jié),是常量池的索引

父類名倔矾,占兩個(gè)字節(jié)妄均,是常量池的索引

接口分兩部分組成,接口數(shù)量哪自,對(duì)應(yīng)的接口索引丰包,從常量池中找

如果接口數(shù)量是0路翻,則沒(méi)有對(duì)應(yīng)的接口索引

字段表集合

字段表用于描述類和接口中聲明的變量弦牡。這里的字段包含了類級(jí)別變量以及實(shí)例變量兜喻,但是不包括方法內(nèi)部聲明的局部變量

fields_count:u2 字段的數(shù)量

每一個(gè)字段的結(jié)構(gòu)

類型 名稱 數(shù)量
u2 access_flags(訪問(wèn)修飾符) 1
u2 name_index(名字的索引) 1
u2 descriptor_index(描述符的索引) 1
u2 attributes_count(字段擁有的獨(dú)有的信息) 1
attribute_info attributes(attributes_count為0赦拘,則無(wú)該類型) attributes_count

上述表格用數(shù)據(jù)結(jié)構(gòu)表示

field_info{
  u2 access_flag;
  u2 name_index;
  u2 descriptor_index;
  attribute_info attributes[attributes_count];
}

名字+描述符可以唯一確定一個(gè)字段饶号,再加上訪問(wèn)修飾符角雷,這個(gè)字段的完整信息確確實(shí)實(shí)就確定下來(lái)了

方發(fā)表

methods_count:u2

方發(fā)表結(jié)構(gòu)

類型 名稱 數(shù)量
u2 access_flags(訪問(wèn)修飾符) 1
u2 name_index(名字的索引) 1
u2 descriptor_index(描述符的索引) 1
u2 attributes_count(方法擁有的獨(dú)有的信息) 1
attribute_info attributes(attributes_count為0绣硝,則無(wú)該類型) attributes_count

上述表格用數(shù)據(jù)結(jié)構(gòu)表示

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

如果我們沒(méi)有編寫構(gòu)造方法,Java編譯器會(huì)自動(dòng)生成一個(gè)無(wú)參的構(gòu)造方法,對(duì)成員變量的賦值,非靜態(tài)代碼塊,是在構(gòu)造方法里面,會(huì)生成相對(duì)應(yīng)的指令
有多個(gè)構(gòu)造方法,會(huì)在每一個(gè)方法里面生成對(duì)應(yīng)的指令,如果我們編寫了自己的構(gòu)造方法,那么也會(huì)生成對(duì)應(yīng)的指令,然后執(zhí)行我們自己的代碼,如果一個(gè)構(gòu)造方法A調(diào)用了另外一個(gè)構(gòu)造方法B,那個(gè)A中不會(huì)生成對(duì)應(yīng)的指令

<clinit>方法是自動(dòng)生成的,對(duì)靜態(tài)信息的初始化(靜態(tài)變量,靜態(tài)代碼塊)
多個(gè)靜態(tài)代碼塊合并成一個(gè)<clinit>方法

屬性結(jié)構(gòu)

屬性結(jié)構(gòu)憔披,每個(gè)屬性都是一個(gè)attribute_info結(jié)構(gòu)矩动,用數(shù)據(jù)結(jié)構(gòu)表示

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

JVM預(yù)定了部分attribute有巧,但是編譯器自己也可以實(shí)現(xiàn)自己的attribute寫入class文件里,供運(yùn)行時(shí)使用

不同的attribute通過(guò)attribute_name_index來(lái)區(qū)分

Code結(jié)構(gòu)

Code attribute的作用是保存該方法的結(jié)構(gòu)悲没,用數(shù)據(jù)結(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ù)篮迎,不包含attribute_name_index和attribute_length字段

max_stack表示這個(gè)方法運(yùn)行的任何時(shí)刻所能達(dá)到的操作數(shù)棧的最大深度

max_locals表示方法執(zhí)行期間創(chuàng)建的局部變量的數(shù)目,包含用來(lái)表示傳入的參數(shù)的局部變量

code_length表示該方法所包含的字節(jié)碼的字節(jié)數(shù)以及具體的指令碼示姿,具體字節(jié)碼即是該方法被調(diào)用時(shí)甜橱,虛擬機(jī)所執(zhí)行的字節(jié)碼。如果安裝了jclasslib插件栈戳,點(diǎn)擊對(duì)應(yīng)的指令可以跳到oracle的幫助文檔查看具體的指令

exception_table岂傲,這里存放的是處理異常的信息

exception_table_length表示異常表的長(zhǎng)度

每個(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表示處理異常的代碼的開始處乃戈。

catch_type表示會(huì)被處理的異常的類型,它指向常量池里的一個(gè)異常類堰乔。當(dāng)catch_type為0時(shí)偏化,表示處理所有的異常

Java字節(jié)碼對(duì)于異常的處理方式

  1. 統(tǒng)一采用異常表的方式對(duì)異常進(jìn)行處理
  2. 在jdk1.4.2之前的版本中,并不是使用異常表的方式來(lái)對(duì)異常進(jìn)行處理的,而是采用特定的指令方式
  3. 當(dāng)異常處理存在finally語(yǔ)句塊時(shí),現(xiàn)代化的JVM采取的處理方式是將finally語(yǔ)句塊的字節(jié)碼拼接到每一個(gè)catch塊后面,換句話說(shuō),程序中存在多少個(gè)catch塊,就會(huì)
    在每一個(gè)catch塊后面重復(fù)多少個(gè)finally語(yǔ)句塊的字節(jié)碼

try catch finally捕獲的異常 與 throws拋出的異常 這兩種在class文件中的位置是不一樣的

LineNumberTable屬性

LineNumberTable,這個(gè)屬性用來(lái)表示code數(shù)組中的字節(jié)碼和Java代碼行數(shù)之間的關(guān)系镐侯,這個(gè)屬性可以用來(lái)在調(diào)試的時(shí)候定位代碼執(zhí)行的行數(shù)

LineNumberTable_attribute{
  u2 attribute_name_index;
  u4 attribute_length;
  u2 line_number_table_length;
  {
    u2 start_pc;// start_pc是這個(gè)line_number_info 描述的字節(jié)碼指令的偏移量
    u2 line_number;// line_number是這個(gè)line_number_info 描述的字節(jié)碼指令對(duì)應(yīng)的源碼中的行號(hào)
  }line_number_table[line_number_table_length];
}

LocalVariableTable屬性

LocalVariableTable侦讨,這個(gè)屬性建立了方法中的局部變量與源代碼中的局部變量之間的對(duì)應(yīng)關(guān)系

LocalVariableTable_attribute{
  u2 attribute_name_index;
  u4 attribute_length;
  u2 local_variable_table_length;
  {
    u2 start_pc;// start_pc是當(dāng)前l(fā)ocal_variable_info所對(duì)應(yīng)的局部變量的作用域的起始字節(jié)碼偏移量;
    u2 length;// length是當(dāng)前l(fā)ocal_variable_info所對(duì)應(yīng)的局部變量的作用域的大小苟翻。 也就是從字節(jié)碼偏移量start_pc 到start_pc+length就是當(dāng)前局部變量的作用域范圍韵卤;
    u2 name_index;// name_index指向常量池中的一個(gè)CONSTANT_Utf8_info, 該CONSTANT_Utf8_info描述了當(dāng)前局部變量的變量名
    u2 descriptor_index;// descriptor_index指向常量池中的一個(gè)CONSTANT_Utf8_info崇猫, 該CONSTANT_Utf8_info描述了當(dāng)前局部變量的描述符
    u2 index;// index描述了在該方法被執(zhí)行時(shí)沈条,當(dāng)前局部變量在棧幀中局部變量表中的位置
  }local_variable_table[local_variable_table_length];
}

在Java中,每一個(gè)方法里面都是可以訪問(wèn)this的诅炉,this表示對(duì)當(dāng)前對(duì)象的引用蜡歹,從字節(jié)碼角度來(lái)說(shuō),如果方法是一個(gè)非靜態(tài)方法涕烧,this實(shí)際上是作為方法的第一個(gè)參數(shù)給隱式傳遞過(guò)來(lái)的月而,this是編譯完成之后,編譯器隱式傳遞過(guò)來(lái)的议纯,這樣父款,每一個(gè)非靜態(tài)方法都可以順利訪問(wèn)this。所以對(duì)于一個(gè)非靜態(tài)方法瞻凤,至少會(huì)有一個(gè)LocalVariableTable憨攒,這個(gè)變量就是this

對(duì)于Java類中的每一個(gè)實(shí)例方法(非static方法),其實(shí)在編譯后所生成的字節(jié)碼當(dāng)中,方法參數(shù)的數(shù)量總是會(huì)比源代碼中方法參數(shù)的數(shù)量多一個(gè),多的參數(shù)是this,他位于方法的第一個(gè)參數(shù)位置處;這樣,我們就可以在Java的實(shí)例方法中使用this來(lái)去訪問(wèn)當(dāng)前對(duì)象的屬性以及其他方法

這個(gè)操作時(shí)在編譯期間完成的,即由javac編譯器在編譯的時(shí)候?qū)?duì)this的訪問(wèn)轉(zhuǎn)化為對(duì)一個(gè)普通實(shí)例方法參數(shù)的訪問(wèn),接下來(lái)在運(yùn)行期間,由JVM在調(diào)用實(shí)例方法時(shí),自動(dòng)向?qū)嵗椒▊魅朐搕his參數(shù),所以,在實(shí)例方法的局部變量表中,至少會(huì)有一個(gè)指向當(dāng)前對(duì)象的局部變量

SourceFileg屬性

SourceFileg,SourceFile屬性是類文件結(jié)構(gòu)的attributes表中的一個(gè)可選的固定長(zhǎng)度屬性,類文件結(jié)構(gòu)的attributes表中最多可以有一個(gè)SourceFile屬性。

SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;
}

StackMapTable屬性

StackMapTable,是Code attributes表中的一個(gè)可變長(zhǎng)度屬性,在通過(guò)類型檢查進(jìn)行驗(yàn)證的過(guò)程中使用阀参。一個(gè)Code attributes表中最多可以有一個(gè)StackMapTable屬性肝集。

在版本號(hào)為50.0或更高版本的類文件中,如果方法的代碼屬性沒(méi)有StackMapTable屬性蛛壳,則它具有隱式堆棧映射屬性包晰。此隱式堆棧映射屬性等效于StackMapTable屬性,其條目數(shù)等于零炕吸。

StackMapTable_attribute {
    u2              attribute_name_index;
    u4              attribute_length;
    u2              number_of_entries;
    stack_map_frame entries[number_of_entries];
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市勉痴,隨后出現(xiàn)的幾起案子赫模,更是在濱河造成了極大的恐慌,老刑警劉巖蒸矛,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瀑罗,死亡現(xiàn)場(chǎng)離奇詭異胸嘴,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)斩祭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門劣像,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人摧玫,你說(shuō)我怎么就攤上這事耳奕。” “怎么了诬像?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵屋群,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我坏挠,道長(zhǎng)芍躏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任降狠,我火速辦了婚禮对竣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榜配。我一直安慰自己否纬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布芥牌。 她就那樣靜靜地躺著烦味,像睡著了一般。 火紅的嫁衣襯著肌膚如雪壁拉。 梳的紋絲不亂的頭發(fā)上谬俄,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音弃理,去河邊找鬼溃论。 笑死,一個(gè)胖子當(dāng)著我的面吹牛痘昌,可吹牛的內(nèi)容都是我干的钥勋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辆苔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼算灸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起驻啤,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤菲驴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后骑冗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赊瞬,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡先煎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了巧涧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薯蝎。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谤绳,靈堂內(nèi)的尸體忽然破棺而出占锯,到底是詐尸還是另有隱情,我是刑警寧澤闷供,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布烟央,位于F島的核電站,受9級(jí)特大地震影響歪脏,放射性物質(zhì)發(fā)生泄漏疑俭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一婿失、第九天 我趴在偏房一處隱蔽的房頂上張望钞艇。 院中可真熱鬧,春花似錦豪硅、人聲如沸哩照。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)飘弧。三九已至,卻和暖如春砚著,著一層夾襖步出監(jiān)牢的瞬間次伶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工稽穆, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冠王,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓舌镶,卻偏偏與公主長(zhǎng)得像柱彻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子餐胀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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