魔數(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)總表
上面的表中描述了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ì)于異常的處理方式
- 統(tǒng)一采用異常表的方式對(duì)異常進(jìn)行處理
- 在jdk1.4.2之前的版本中,并不是使用異常表的方式來(lái)對(duì)異常進(jìn)行處理的,而是采用特定的指令方式
- 當(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];
}