超贊,2W 字的Java class類文件結(jié)構(gòu)詳解!
原文:cnblogs.com/insaneXs/p/13301094.html
最近在二刷《深入理解Java虛擬機(jī)》的時(shí)候,發(fā)現(xiàn)看類文件結(jié)構(gòu)這章依舊云里霧里。因?yàn)轭愇募械慕Y(jié)構(gòu)實(shí)在太多了,尤其在涉及表的時(shí)候,經(jīng)常會(huì)出現(xiàn)表中嵌套表的情況。
有學(xué)習(xí)經(jīng)驗(yàn)的同學(xué)一定知道最快了解一種協(xié)議的方法就是參照規(guī)則自己將協(xié)議解析一次。眾所周知類文件中存的也是字節(jié)(所以class文件也叫字節(jié)碼文件),針對(duì)類文件結(jié)構(gòu)的解析就像是對(duì)某種協(xié)議的解析。因此,可以將《深入理解Java虛擬機(jī)》第6章中的內(nèi)容作為工具書來對(duì)照(因?yàn)闀刑峁┝烁鞣N結(jié)構(gòu)的說明和解析規(guī)則),自己寫一個(gè)類,針對(duì)編譯后產(chǎn)生的class文件解析一遍就能對(duì)類文件結(jié)構(gòu)有個(gè)大致的印象了。
類文件結(jié)構(gòu):
在開始解析類文件前,需要先對(duì)class文件的大致結(jié)構(gòu)做一個(gè)初步了解,后續(xù)的解析也將會(huì)根據(jù)這個(gè)結(jié)構(gòu)分成幾部分來解析。
校驗(yàn)信息: 魔數(shù):4字節(jié)(固定值0xcafe babe) 次版本號(hào):2字節(jié) 主版本號(hào):2字節(jié),由十進(jìn)制的45開始。 常量池: 常量池計(jì)數(shù)( constant_pool_count):2字節(jié),表示常量池中常量的個(gè)數(shù),其中下標(biāo)為0的常量并不會(huì)出現(xiàn)在常量池中,也就是說常量池中的索引實(shí)際上是從1開始的,之所以要將下標(biāo)為0的常量預(yù)留是為了滿足常量池中某一常量不引用任何常量的情況(Java語言中是否有這種情況還需要確認(rèn))。常量池( constant_pool):每個(gè)常量的數(shù)據(jù)結(jié)構(gòu)并不是固定的,由常量的類型決定,但是每個(gè)常量的第一個(gè)字節(jié)決定了常量的類型。類信息: 訪問標(biāo)志( access_flags):2字節(jié),表示類和接口的訪問控制類信息( this_class):2字節(jié)父類信息( super_class):2字節(jié)接口信息,包括接口個(gè)數(shù)( interfaces_count, 2字節(jié))和接口信息(interfaces?一個(gè)interface是2字節(jié),總共interfaces_counts?* 2個(gè)字節(jié))字段信息: 字段個(gè)數(shù)( fields_count):2字節(jié),表示之后有幾個(gè)field_info的結(jié)構(gòu)字段表( field_info):字段表,是一個(gè)表結(jié)構(gòu),長度不固定,結(jié)構(gòu)稍復(fù)雜方法信息: 方法個(gè)數(shù)( methods_count):2字節(jié),表示之后有幾個(gè)(method_info)方法表結(jié)構(gòu)方法表( methods_info):方法表,長度不固定,可能是最復(fù)雜的一個(gè)結(jié)構(gòu)了。屬性表信息: 屬性表個(gè)數(shù)( attributes_count):2字節(jié),表示之后有幾個(gè)屬性表結(jié)構(gòu)屬性表( attribute_info)屬性表信息,表結(jié)構(gòu),長度不固定。屬性表信息可能會(huì)同樣嵌套在字段表,方法表的表結(jié)構(gòu)中。
對(duì)Class文件解析前的準(zhǔn)備
對(duì)Class文件的解析首先需要寫一個(gè)類用作測(cè)試,然后編譯該類生成class文件,在針對(duì)該class文件進(jìn)行解析。
為了更好的對(duì)比解析過程的正確性,我們可以通過javap -p -verbose命令先對(duì)class文件反編譯,輸出結(jié)果。
Java代碼:
package?com.insanexs.mess.javap;
public?class?JavapTest?{
????protected?static?final?String?VAR_CONSTANT?=?"CONSTANT";
????private?volatile?int?intField;
????private?int[]?intArraysField;
????private?String?strField;
????public?JavapTest(){
????}
????public?void?publicMethod(){
????}
????protected?String?protectedReturnStrMethod(){
????????return?strField;
????}
????private?synchronized?void?privateSynchronizedMethod(int?intArgs){
????????intField?=?intArgs;
????}
}
編譯生成的class文件如下(已經(jīng)按各部分拆開):

開始分析
校驗(yàn)信息
魔數(shù)
魔數(shù)是class文件的前四個(gè)字節(jié),固定為0xcafe babe。用途是判斷文件是否正確。
主次版本號(hào)
0x0000 0034 其中前兩個(gè)字節(jié)表示次版本號(hào),后兩個(gè)字節(jié)表示主版本號(hào)。0x34轉(zhuǎn)成十進(jìn)制為52,52-45+1 = 8(第一個(gè)主版本號(hào)從45開始),因此推斷出Java版本為JDK 8。主次版本號(hào)用來校驗(yàn)字節(jié)碼和JVM是否匹配,JVM的版本需要大于等于class文件的版本號(hào)。
常量池
常量池中存量了兩種類型的常量:字面量和符號(hào)引用。
字面量可以理解為常量的值(無法修改的值)。
符號(hào)引用則包括三種類型:類或接口的全限定名,字段的名稱和描述符,方法的名稱和描述符(后兩種情況其實(shí)是很對(duì)稱的,一個(gè)針對(duì)字段另一個(gè)針對(duì)方法)。
常量池計(jì)數(shù)constant_pool_count
校驗(yàn)信息后,接下來的兩個(gè)字節(jié)表示常量池中常量的個(gè)數(shù),這里是0x0024,轉(zhuǎn)成十進(jìn)制為36。說明常量池中共有36個(gè)常量。
但是由于下標(biāo)為0的常量是由JVM故意空出的,不會(huì)顯示出現(xiàn)在字節(jié)碼中,因此實(shí)際常量池的常量從1開始,直到35。
常量池
我們已經(jīng)知道了常量池共有36個(gè)常量(實(shí)際只有35個(gè)),但由于不同類型的常量結(jié)構(gòu)并不是固定的,我們無法通過常量個(gè)數(shù)直接推出之后多少字節(jié)屬于常量池的內(nèi)容。
因此,我們只能逐個(gè)解析常量池中的常量,直到解析出常量的個(gè)數(shù)達(dá)到constant_pool_count - 1,常量池才算解析完成。
由于篇幅限制,本文不會(huì)解析全部常量池,只會(huì)解析其中一些常量用作示范,讀者可根據(jù)示范自行完成剩下的常量解析。
上面已經(jīng)說了不同類型的常量其結(jié)構(gòu)也是不同的,但是所有常量的第一個(gè)字節(jié)都是標(biāo)志位,因此解析常量池中的常量的方式是解析一個(gè)字節(jié),根據(jù)該字節(jié)確定常量的類型,在查找對(duì)應(yīng)的結(jié)構(gòu)完成剩余部分的解析。
譬如,這里的第一個(gè)常量#1:
第一個(gè)字節(jié):0x0a,對(duì)比常量池的類型后發(fā)現(xiàn)是一個(gè)CONSTANT_Methodref_info(類中方法的符號(hào)引用)。
在查找該類型的結(jié)構(gòu),應(yīng)該是:
CONSTANT_Methodref_info?{
?u1?tag;
?u2?class_index;
?u2?name_and_type_index;
}
應(yīng)該是一個(gè)字節(jié)的標(biāo)識(shí)位(tag),2個(gè)字節(jié)的類名稱索引(class_index)和2個(gè)字節(jié)的名稱和類型描述符(name_and_type_index),共5個(gè)字節(jié)。
因此常量池#1對(duì)應(yīng)的字節(jié)分別是:0a 0005 001e(含我們已經(jīng)分析過的0a)。其中class_index?為0x0005,name_and_type_index為0x001e。這兩個(gè)index的值均是指向常量池的其他常量的,轉(zhuǎn)成十進(jìn)制分別是指向#5和#30。
這樣就解析完了常量池中的第一個(gè)常量。
//常量#1
0a //tag 表示類型為Methodref_info(類中方法) 其結(jié)構(gòu)為(U1 flag;U2 index(指向類描述符); U2 index(指向名稱和類型描述符))
0005 //指向常量池中0x05的常量 => #5
001e //指向常量池中0x1e的常量 => #30
同理我們解析常量池中的第二個(gè)常量#2:
首先,第一個(gè)字節(jié)是0x09,查表確定類型為CONSTANT_Fieldref_info(字段的符號(hào)引用)。確定其結(jié)構(gòu)和CONSTANT_Methodref_info相同:
CONSTANT_Fieldref_info?{
?u1?tag;
?u2?class_index;
?u2?name_and_type_index;
}
同樣解析得:
//常量#2
09?//0x09表示類型為Fieldref_info(類中字段)?其結(jié)構(gòu)為(U1?flag;?U2?index(指向類描述符);?U2?index(指向名稱和類型描述))=>
0004?//指向常量池中0x04的常量?=>#4
001f?//指向常量池中0x1f的常量?=>#31
之后的解析過程不再贅述,直接貼上解析常量池#3-#35的結(jié)果:
//常量#3
09?//同樣是Fieldref_info
0004?//?=>#4
0020?//?=>#32
//常量#4
07?//0x07表示Class_info(類或接口的符號(hào)引用)?其結(jié)構(gòu)為(U1?flag;?U2?index(指向全限定名常量))
0021?//指向常量池中的0x21?=>?#33
//常量#5
07?//同樣是Class_info類型
0022?//?=>?#34
//常量#6
01?//0x01表示Utf8_info?表示一個(gè)UTF8字符串常量(U1?flag;?U2?length(字符串占的字節(jié)數(shù));?U1?數(shù)量為length個(gè),表示byte)說明理論上JVM的字符串常量的字節(jié)上線為65535???
000c?//length?=?0x0c?表示之后12個(gè)字節(jié)是字符串常量字節(jié)內(nèi)容
5641?525f?434f?4e53?5441?4e54?//UTF-8字符串的內(nèi)容?用工具翻譯成字符串表示為:VAR_CONSTANT
//常量#7
01?//同樣是Utf8_info
0012?//length?=?18
4c6a?6176?612f?6c61?6e67?2f53?7472?696e?673b?//翻譯成字符串為:Ljava/lang/String;
//常量#8
01?//同樣是Utf8_info
000d?//length?=?13
436f?6e73?7461?6e74?5661?6c75?65?//翻譯成字符串為:ConstantValue
//常量#9
08?//0x08表示String_info?表示字符串字面常量(U1?flag;?U2?index(指向字符串字面量))
0023?//指向常量池中的0x23?=>#35
//常量#10
01?//Utf8_info
0008?//length?=?8
696e?7446?6965?6c64?//翻譯成字符串:intField
//常量#11
01?//Utf8_info
0001?//length?=?1
49?//翻譯成字符串:I
//常量#12
01?//Utf8_info
000e?//length?=?14
696e?7441?7272?6179?7346?6965?6c64?//翻譯成字符串為:intArraysField
//常量#13
01?//Utf8_info
0002?//length?=?2
5b49?//翻譯成字符串為:[I
//常量#14
01?//Utf8_info
0008?//length?=?8
7374?7246?6965?6c64?//翻譯成字符串為:strField
//常量#15
01?//Utf8_info
0006?//length?=?6
3c69?6e69?743e?//翻譯成字符串為:
//常量#16
01?//Utf8_info
0003?//length?=?3
2829?56?//翻譯成字符串為:()V
//常量#17
01?//Utf8_info
0004?//?length?=?4
436f?6465?//翻譯成字符串為:Code
//常量#18
01?//Utf8_info
000f?//length?=?15
4c69?6e65?4e75?6d62?6572?5461?626c?65?//翻譯成字符串為:LineNumberTable
//常量#19
01?//Utf8_info
0012?//length?=?18
4c6f?6361?6c56?6172?6961?626c?6554?6162?6c65?//翻譯成字符串為:LocalVariableTable
/常量#20
01?//Utf8_info
0004?//length?=?4
7468?6973?//翻譯成字符串為:this
//常量#21
01?//Utf8_info
0023?//length?=?35
4c?636f?6d2f?696e?7361?6e65?7873?2f6d?6573?732f?6a61?7661?702f?4a61?7661?7054?6573?743b?//翻譯成字符串為:Lcom/insanexs/mess/javap/JavapTest;
//常量#22
01?//Utf8_info
000c?//length?=?12
70?7562?6c69?634d?6574?686f?64?//翻譯成字符串為:publicMethod
//常量#23
01?//Utf8_info
0018?//length?=?24
7072?6f74?6563?7465?6452?6574?7572?6e53?7472?4d65?7468?6f64?//翻譯成字符串為:protectedReturnStrMethod
//常量#24
01?//Utf8_info
0014?//length?=?20
28?294c?6a61?7661?2f6c?616e?672f?5374?7269?6e67?3b?//翻譯成字符串為:()Ljava/lang/String;
//常量#25
01?//Utf8_info
0019?//length?=?25
7072?6976?6174?6553?796e?6368?726f?6e69?7a65?644d?6574?686f?64?//翻譯成字符串為:privateSynchronizedMethod
//常量#26
01?//Utf8_info
0004?//length?=?4
2849?2956?//翻譯成字符串為:(I)V
//常量#27
01?//Utf8_info
0007?//length?=?7
69?6e74?4172?6773?//翻譯成字符串為:intArgs
//常量#28
01?//Utf8_info
000a?//length?=?10
53?6f75?7263?6546?696c?65?//翻譯成字符串為:SourceFile
//常量#29
01?//Utf8_info
000e?//length?=?14
4a61?7661?7054?6573?742e?6a61?7661?//翻譯成字符串為:JavapTest.java
//常量#30
0c?//0x0c表示NameAndType_info?表示字段或方法的部分符號(hào)引用(U1?flag;?U2?index(指向字段或方法的名稱常量);?U2?index(指向字段或方法的描述符常量))
000f?//指向常量池中的0x0f?=>?#15
0010?//指向常量池中的0x10?=>?#16
//常量#31
0c?//同樣是NameAndType_info
000e?//指向常量池中的0x0e?=>?#14
0007?//指向常量池中的0x07?=>?#7
//常量#32
0c?//同樣是NameAndType_info
000a?//指向常量池中的0x0a?=>?#10
000b?//指向常量池中的0x0b?=>?#11
//常量#33
01?//Utf8_info
0021?//length?=?33
636f?6d2f?696e?7361?6e65?7873?2f6d?6573?732f?6a61?7661?702f?4a61?7661?7054?6573?74?//翻譯成字符串為:com/insanexs/mess/javap/JavapTest
//常量#34
01?//Utf8_info
0010?//length?=?16
6a61?7661?2f6c?616e?672f?4f62?6a65?6374?//翻譯成字符串為:java/lang/Object
//常量#35
01?//Utf8_info
0008?//length?=?8
43?4f4e?5354?414e?54?//翻譯成字符串為:CONSTANT
上述常量池得很多常量都直接(或是比較直接的)出現(xiàn)在我們得代碼中,比如字段名稱,類名稱,方法全限定名等。有一部分是根據(jù)代碼能推測(cè),比如方法描述符等,但是還有一部分似乎不明所以,比如上述的CODE,LineNumberTable等。
不著急,這些常量在之后得解析中會(huì)再次遇到。
類信息
在解析完常量池的數(shù)據(jù)后,接下來的一部分?jǐn)?shù)據(jù)表示類得一些信息。從上述得字節(jié)碼得0x0021開始(29行的最后一組)
類信息這部分主要有類的訪問控制屬性,類索引和父類索引(指向常量池中的常量,通常是類得全限定名),接口個(gè)數(shù)和接口索引(接口索引同樣指向常量池中的常量)。
訪問控制access_flag
訪問控制占兩個(gè)字節(jié)(16位,每一個(gè)二進(jìn)制代表一種標(biāo)志,因此理論上最多能有16種標(biāo)志,Java SE 8中定義了8種)。上述文件中對(duì)應(yīng)訪問控制標(biāo)志的字節(jié)位0x0021。0x0021 = (0x0020 | 0x0001),查表得出ACC_SUPER,ACC_PUBLIC這個(gè)類的訪問控制標(biāo)志位?,F(xiàn)代版本編譯的類都會(huì)帶有ACC_SUPER,而ACC_PUBLIC表示這個(gè)類是public的。
類索引this_class
類索引占兩個(gè)字節(jié),同樣指向常量池中的常量(類型為類的符號(hào)飲用)。這里對(duì)應(yīng)的數(shù)據(jù)為:
0004 //this_class U2,指向常量池中的Class_info 這里指向常量池#4
父類索引super_class
父類索引和類索引類似,同樣占兩個(gè)字節(jié),指向常量池中的常量,只不過指向的類的符號(hào)飲用代碼的是父類,這里對(duì)應(yīng)的數(shù)據(jù)為:
0005 //super_class U2 同樣指向常量池中的Class_info 這里指向常量池#5
接口個(gè)數(shù)interfaces_count和接口索引interface
之后的兩個(gè)字節(jié)表示類實(shí)現(xiàn)的接口的個(gè)數(shù),然后對(duì)應(yīng)的interfaces_count * 2個(gè)字節(jié)表示接口的索引數(shù)據(jù)。由于我們的測(cè)試類沒有實(shí)現(xiàn)任何接口,因此interfaces_counts為0,之后也沒有表示接口數(shù)據(jù)的字節(jié)。
0000 //interface_count 表示接口的個(gè)數(shù) 這里為0 表示類沒有實(shí)現(xiàn)接口,之后也沒有字節(jié)表示接口索引
字段信息
分析完類信息后,之后的數(shù)據(jù)表示類中字段的信息。這里分為兩部分:字段個(gè)數(shù)field_count和字段表field_info。
字段個(gè)數(shù)field_count
字段個(gè)數(shù)表示后面將會(huì)有幾個(gè)字段表結(jié)構(gòu),因?yàn)樽侄伪斫Y(jié)構(gòu)長度也不是固定的,因此也只能解析完所有的字段表后才能繼續(xù)解析下一部分內(nèi)容,無法直接通過字段個(gè)數(shù)推出之后的多少字節(jié)表示字段信息相關(guān)的數(shù)據(jù)。
字段個(gè)數(shù)占兩個(gè)字節(jié)。
0004 //field_count 表示字段的個(gè)數(shù) 這里為4 表示接下來的4個(gè)字段表結(jié)構(gòu)
字段表field_info
字段表接口較為復(fù)雜,因?yàn)槠涫且粋€(gè)表接口,且可能嵌套其他表。
先來看一下字段表的接口:
field_info?{
?u2?access_flags;
?u2?name_index;
?u2?descriptor_index;
?u2?attributes_count;
?attribute_info?attributes[attributes_count];
}
access_flags占兩字節(jié),表示字段的訪問屬性,name_index占兩字節(jié),指向常量池中的常量,表示字段名稱,descriptor_index占兩字節(jié),同樣指向常量池中的常量,表示字段的描述符,attributes_count占兩字節(jié),表示后面有幾個(gè)屬性表,attribute_info即屬性表,用來描述額外屬性,為表結(jié)構(gòu),長度不固定。
前文已知我們的測(cè)試類會(huì)有4個(gè)字段表,我們這里只針對(duì)第一個(gè)字段表分析,后續(xù)的讀者可以自己按規(guī)則解析。
首先分析固定的前八個(gè)字節(jié):
001c //access_flags 字段的訪問屬性 0x1c = (0x10 | 0x08 | 0x04 ) =>ACC_PROTECTED ACC_FINAL ACC_STATIC
0006 //name_index 指向常量池中#6 即變量名為VAR_CONSTANT
0007 //descriptor_index 指向常量池#7 即描述符Ljava/lang/String; 說明是String類型的字段
0001 //attributes_count 表示有1個(gè)attribute_info
如果某個(gè)字段代表的attributes_count的字節(jié)值為0,那么對(duì)這個(gè)字段的解析就已經(jīng)完成了,但是好巧不巧的,這里分析的attributes_count為1,說明之后還有一些字節(jié)是用來表示屬性表的。
屬性表用來描述某些特定的額外信息,其整個(gè)結(jié)構(gòu)并非是固定長度的,甚至可能屬性表中嵌套屬性表的情況。
了解下屬性表的通用結(jié)構(gòu):
attribute_info?{
?u2?attribute_name_index;
?u4?attribute_length;
?u1?info[attribute_length];
}
attribute_name_index:2字節(jié),指向常量池中的常量,表示常量的名稱(Java SE 8規(guī)定能識(shí)別的常量類型有23種)。attribute_length:4字節(jié),表示之后還有多少長度的字節(jié)均數(shù)據(jù)該屬性表的內(nèi)容info:不固定長字節(jié),解析方式由屬性表的類型決定。
可以看到前六個(gè)字節(jié)都是相同的,然后由attribute_length表示之后還有多少個(gè)字節(jié),這一點(diǎn)和一些變長協(xié)議類似。
回到我們需要解析的數(shù)據(jù)中來,屬于字段1中屬性表的字節(jié)應(yīng)該是0008 0000 0002 0009。
通過解析前兩個(gè)字節(jié)0008,我們得知其指向常量池中的#8,為即ConstantValue。這是當(dāng)字段被final修飾后,出現(xiàn)在字段中的屬性表,表示一個(gè)常量。該屬性表的結(jié)構(gòu)如下:
ConstantValue_attribute?{
?u2?attribute_name_index;
?u4?attribute_length;
?u2?constantvalue_index;
}
可以看到其代表attribute_length的四字節(jié)數(shù)據(jù)為0000 0002(因?yàn)?code style="margin-right: 2px;margin-left: 2px;padding-right: 2px;padding-left: 2px;outline: 0px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);border-radius: 2px;height: 21px;line-height: 22px;">constantvalue_index的長度固定為2),對(duì)比我們的字節(jié)數(shù)據(jù)也確實(shí)如此。然后我們解析代表constantvalue_index的字節(jié)0009,表示指向常量池中#9,為CONSTANT。正好是常量的值。
之后還剩三個(gè)字段,有興趣的讀者可以自己分析,這里直接貼上結(jié)果:
//field1
001c?//access_flags?字段的訪問屬性?0x1c?=?(0x10?|?0x08?|?0x04?)?=>ACC_PROTECTED?ACC_FINAL?ACC_STATIC
0006?//name_index?指向常量池中0x06?=>?#6?即VAR_CONSTANT
0007?//descriptor_index?指向常量池中0x07?=>?#7?即Ljava/lang/String;?說明是String類型的字段
0001?//attributes_count?表示有1個(gè)attribute_info?屬性表?attribute_info是一個(gè)比較復(fù)雜的結(jié)構(gòu),虛擬機(jī)規(guī)范中定義了虛擬機(jī)應(yīng)當(dāng)識(shí)別的二十多種屬性(Java?SE?8?23種)所有屬性的開始的6字節(jié)都是相同的(U2?attribute_name_index?+?U4?attribute_length),之后的結(jié)構(gòu)由屬性自己定義,屬性表可以出現(xiàn)在類,字段及方法上
0008?//attribute_name_index?指向常量池中的Utf8_info常量?0x08?=>?#8?即ConstantValue?ConstantValue是屬性表的一種,出現(xiàn)在字段中,表示final定義的常量值
0000?0002?//length?=?2?表示后面2個(gè)字節(jié)長度的數(shù)據(jù)為該屬性表的數(shù)據(jù)
0009?//對(duì)于ConstantValue而言?這部分?jǐn)?shù)據(jù)表示constantvalue_index?指向常量池中的常量?0x09即?=>#9?即String_info?具體值為#35?為字符串?CONSTANT
//filed2
0042?//access_flags?=>(0x40?|?0x02)?=>?ACC_PRIVATE?ACC_VOLATILE
000a?//name_index?指向常量池中的0x0a?#10?即intField
000b?//descriptor_index?指向常量池中的0x0b?#11?即I?表示int類型的field
0000?//attribute_count?=?0?說明無attribute_info
//filed3
0002?//access_flags?=>?0x02?=>ACC_PRIVATE
000c?//name_index?指向常量池中的0x0c?#12?即intArraysField
000d?//descriptor_index?指向常量池中的0x0d?#13?即[I?表示int數(shù)組
0000?//attribute_count?=?0?說明無attribute_info
//field4
0002?//access_flags?=>?0x02?=>ACC_PRIVATE
000e?//name_index?指向常量池中的0x0e?#14?即strField
0007?//descriptor_index?指向常量池中的0x07?#7?即?Ljava/lang/String;?說明是String類型的字段
0000?//attribute_count?=?0?說明無attribute_info
方法信息
解析完字段信息,之后的字節(jié)是從0x0004開始(32行最后一個(gè)字節(jié)和33行第一個(gè)字節(jié)),方法信息和字段信息的解析其實(shí)很對(duì)成,同樣顯示通過methods_count表示之后有幾個(gè)方法表,再逐個(gè)解析方法表method_info,直到達(dá)到方法個(gè)數(shù)。方法表的整體結(jié)構(gòu)和字段表的整體結(jié)構(gòu)也是類似的,只是方法表上的屬性表會(huì)更多,因此,解析起來要比字段復(fù)雜。
方法個(gè)數(shù)methods_count
methods_count占兩個(gè)字節(jié),表示之后共有多少個(gè)方法表。0004表示之后有四個(gè)方法表。
方法表method_info
方法表和字段表結(jié)構(gòu)是對(duì)稱的,通用結(jié)構(gòu)如下:
method_info?{
?u2?access_flags;
?u2?name_index;
?u2?descriptor_index;
?u2?attributes_count;
?attribute_info?attributes[attributes_count];
}
可以看到其結(jié)構(gòu)和field_info是相同的,只不過name_index指向常量池中表示的方法名稱的常量,descriptor_index則指向常量池中表示方法描述符的常量。
我們以第一個(gè)方法表為例,先解析前八個(gè)固定的字節(jié)。access_flag為0001,對(duì)應(yīng)標(biāo)識(shí)位為ACC_PUBLIC,表示為共有方法。name_index對(duì)應(yīng)的字節(jié)為000f,指向常量池#15,即方法名為:,descriptor_index為0010,指向常量池的#16,即方法描述符為:()V 無參無返回。
attributes_count為0001,表示之后有一個(gè)attribute_info屬性表。
屬性表的解析方式在解析字段過程時(shí),已經(jīng)介紹過了。
首先是根據(jù)前兩字節(jié)確認(rèn)屬性表的類型:0011,指向常量池中#17,發(fā)現(xiàn)是我們之前不明所以的CODE常量,原來是一種屬性表的類型。
?CODE屬性表是很重要的一部分信息,因?yàn)樗蟹椒ǖ膱?zhí)行邏輯(代碼塊)。
?
查看CODE屬性表的結(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];
}
可以看到CODE屬性表是一個(gè)非固定長度的結(jié)構(gòu)。
先看相對(duì)簡單的部分:
attribute_name_index:屬性表名稱索引,2字節(jié),表示屬性表的類型。attribute_length:屬性長度,4字節(jié),表示之后多少個(gè)字節(jié)的內(nèi)容均屬于該屬性表。對(duì)于CODE這種非固定長度的屬性表結(jié)構(gòu)而言,長度顯得格外重要。max_stack:2字節(jié),表示操作數(shù)棧的最大深度(注意和方法的調(diào)用棧深度是不同的概念)。max_locals:2字節(jié),局部變量表的大小,單位是slot,一個(gè)slot可以存放32位長度的數(shù)據(jù),但是像long和double類型的變量,需要使用兩個(gè)slot。code_length:字節(jié)碼長度,表示之后n個(gè)字節(jié)均和JVM字節(jié)碼指令相關(guān)。code:字節(jié)碼,JVM字節(jié)碼指令占一個(gè)字節(jié)。但是部分字節(jié)碼指令需要帶上參數(shù)(因此消耗了部分字節(jié)數(shù)據(jù))。
參考例子,對(duì)這部分結(jié)果的解析如下:
0011?//attribute_name_index?指向常量池中的0x11?#17?即Code?表示CODE屬性表?CODE屬性表結(jié)構(gòu)為(U2?attribute_name_index?+?U4?attribute_length?+?U2?max_stack?+?U2?max_locals?+?U4?code_length?+?code_length?*?U1?code?+??U2?exception_table_length?+?exception_table_length?*?exception_info?+?U2?attribute_count?+?attribute_count?*?attribute_info)
0000?0033?//length?=?0x33?表示之后51個(gè)字節(jié)為CODE屬性表中的信息
0001?//max_stack?1
0001?//max_locals?1
0000?0005?//code_length?5?后面接CODE?每個(gè)指令占一個(gè)字節(jié)?部分指令后的字節(jié)表示指令的參數(shù)
2a?b7?00?01?b1?//aload_0?invokespecial?(0001?=>?常量池#1)?return
需要注意的是指令部分b7后面兩個(gè)字節(jié)0001是b7(invokespecial)需要的參數(shù),指向常量池#1。因此并非CODE中的每一個(gè)字節(jié)都是指令集中的指令。
對(duì)異常表的解析
我們這里的例子并沒有涉及異常處理的代碼,因此exception_table_length對(duì)應(yīng)的字節(jié)是0000,長度為0,說明之后沒有異常表。
讀者如果感興趣可以自己寫代碼測(cè)試,分析方法類似。
CODE中嵌套屬性表的解析
方法表結(jié)構(gòu)之所以復(fù)雜,正是因?yàn)榻?jīng)常出現(xiàn)表嵌套表的情況。CODE屬性表就可能含有其他屬性表。
同理,我們解析之后表示attributes_count的兩個(gè)字節(jié)——0002。一看還不少,我激動(dòng)的咽了咽口水,解析不怕多,就是干!
第一個(gè)屬性表的類型索引0012,表示指向常量池#18,為LineNumberTable(又出現(xiàn)了一個(gè)我們之前不明所以的常量,看來這些常量多半是屬性表的類型),表示指令和行號(hào)的對(duì)應(yīng)關(guān)系。查找其結(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];
}
得,結(jié)構(gòu)中前面幾個(gè)還好,后面又嵌套了一個(gè)比較復(fù)雜的屬性。但是沒在怕。attribute_name_index:這個(gè)現(xiàn)在已經(jīng)很面熟了,表示屬性表的名稱索引。attribute_length:這個(gè)也是老伙計(jì),后面多少字節(jié)依舊是該屬性表得內(nèi)容。line_number_table_length:4字節(jié),表示代碼行號(hào)表的個(gè)數(shù),之后有幾個(gè)line_number_table。line_number_table:代碼行號(hào)表,一個(gè)復(fù)雜結(jié)構(gòu),但是好在是固定的,占4字節(jié),前兩字節(jié)start_pc表示一個(gè)偏移量,應(yīng)該是對(duì)著之前分析的CODE屬性表中的code部分順序(要注意區(qū)分這里的兩個(gè)CODE,大寫的CODE表示屬性表的一種類型,小寫的code表示CODE屬性表中表示字節(jié)碼指令的部分)。后兩個(gè)字節(jié)line_number表示指令對(duì)應(yīng)出現(xiàn)在代碼中的行號(hào)。
0012?//attribute_name_index?指向常量池中的0x12?即#18?LineNumberTable?表示行號(hào)和字節(jié)碼指令的對(duì)應(yīng)關(guān)系
0000?000a?//attribute_length?表示后面10個(gè)字節(jié)均為該屬性表的信息
0002?//line_number_table_length?表示后面有2個(gè)line_number_table?一個(gè)line_number_table結(jié)構(gòu)為(U2?start_pc?+?U2?line_number)
0000?//start_pc?start_pc表示上述指令集中的索引?0對(duì)應(yīng)上述指令集既為2a?為aload_0指令
000d?//line_number?等于行號(hào)line:13?表示aload_0?對(duì)應(yīng)代碼13行
0004?//start_pc?同理對(duì)應(yīng)索引為4的指令?return
000f?//line_number?等于行號(hào)line:15?表示return?對(duì)應(yīng)代碼15行
在解析第二個(gè)屬性表:attribute_name_index代表的字節(jié)是0013,對(duì)應(yīng)常量池#19,為LocalVariableTable 表示方法局部變量的描述。
其結(jié)構(gòu)如下:
LocalVariableTable_attribute?{
?u2?attribute_name_index;
?u4?attribute_length;
?u2?local_variable_table_length;
?{?u2?start_pc;
?u2?length;
?u2?name_index;
?u2?descriptor_index;
?u2?index;
?}?local_variable_table[local_variable_table_length];
}
前兩個(gè)部分已經(jīng)很熟悉了不再贅述。local_variable_table_length2字節(jié),表示局部變量表的個(gè)數(shù)。local_variable_table是一個(gè)復(fù)雜結(jié)構(gòu),但也是固定長度的,共10字節(jié)。start_pc占2字節(jié),表示偏移量,length占2字節(jié),表示長度,這兩部分信息結(jié)合起來可以確認(rèn)變量的作用域是從start_pc到start_pc+length。name_index表示2字節(jié),變量的名稱索引,指向常量池中的常量。descriptor_index表示2字節(jié),變量的描述符索引,指向常量池中的常量。這兩部分信息結(jié)合起來可以確定變量的名稱和類型。index表示變量在局部變量表中的索引,前文已經(jīng)介紹過了變量存儲(chǔ)在局部變量中是以slot為單位,這里的index就表示該變量存放在第幾個(gè)slot。
理論分析了,結(jié)合情況實(shí)踐一下:
0013?//attribute_name_index?指向常量池中的0x13?即#19??LocalVariableTable?表示方法局部變量的描述
0000?000c?//attribute_length?表示之后12個(gè)字節(jié)均為?LocalVariableTable?屬性表中的內(nèi)容
0001?//local_variable_table_length?表示有一個(gè)局部變量表?local_variable_table的結(jié)構(gòu)為(U2?start_pc?+?U2?length?+?U2?name_index?+?U2?descriptor_index?+?U2?index)
0000?//start_pc?0
0005?//length?5?說明該局部變量從偏移量0開始到0+5?一直被使用
0014?//name_index?指向常量池中的常量?0x14?=>?#20?即this
0015?//descriptor_index?指向常量池中的常量?0x15?=>?#21?即Lcom/insanexs/mess/javap/JavapTest;
0000?//index?0
?補(bǔ)充說明一點(diǎn):類中的非靜態(tài)方法 虛擬機(jī)會(huì)默認(rèn)將this指針作為方法的第一個(gè)變量。
?
這樣我們第一個(gè)方法就解析完成了,讀者感興趣的可以針對(duì)剩下的三個(gè)方法實(shí)操一下,這里就直接貼上解析結(jié)果:
//method_1
0001?//access_flag?=>?0x01?=>?ACC_PUBLIC
000f?//name_index 指向常量池中的0x0f #15 即方法名為:
0010?//descriptor_index 指向常量池中的0x10?#16 即方法描述符為:()V 無參無返回
0001?//attributes_count?表示之后有一個(gè)attribute_info
0011?//attribute_name_index?指向常量池中的0x11?#17?即Code?表示CODE屬性表?CODE屬性表結(jié)構(gòu)為(U2?attribute_name_index?+?U4?attribute_length?+?U2?max_stack?+?U2?max_locals?+?U4?code_length?+?code_length?*?U1?code?+??U2?exception_table_length?+?exception_table_length?*?exception_info?+?U2?attribute_count?+?attribute_count?*?attribute_info)
0000?0033?//length?=?0x33?表示之后51個(gè)字節(jié)為CODE屬性表中的信息
0001?//max_stack?1
0001?//max_locals?1
0000?0005?//code_length?5?后面接CODE?每個(gè)指令占一個(gè)字節(jié)?部分指令后的字節(jié)表示指令的參數(shù)
2a?b7?00?01?b1?//aload_0?invokespecial?(0001?=>?常量池#1)?return
0000?//exception_table_length?=?0?說明沒有異常表的數(shù)據(jù)?如果exception_table_length為n?后面的n個(gè)字節(jié)為異常表相關(guān)的信息
0002?//attributes_count?=?2
0012?//attribute_name_index?指向常量池中的0x12?即#18?LineNumberTable?表示行號(hào)和字節(jié)碼指令的對(duì)應(yīng)關(guān)系
0000?000a?//attribute_length?表示后面10個(gè)字節(jié)均為該屬性表的信息
0002?//line_number_table_length?表示后面有2個(gè)line_number_table?一個(gè)line_number_table結(jié)構(gòu)為(U2?start_pc?+?U2?line_number)
0000?//start_pc?start_pc表示上述指令集中的索引?0對(duì)應(yīng)上述指令集既為2a?為aload_0指令
000d?//line_number?等于行號(hào)line:13?表示aload_0?對(duì)應(yīng)代碼13行
0004?//start_pc?同理對(duì)應(yīng)索引為4的指令?return
000f?//line_number?等于行號(hào)line:15?表示return?對(duì)應(yīng)代碼15行
0013?//attribute_name_index?指向常量池中的0x13?即#19??LocalVariableTable?表示方法局部變量的描述
0000?000c?//attribute_length?表示之后12個(gè)字節(jié)均為?LocalVariableTable?屬性表中的內(nèi)容
0001?//local_variable_table_length?表示有一個(gè)局部變量表?local_variable_table的結(jié)構(gòu)為(U2?start_pc?+?U2?length?+?U2?name_index?+?U2?descriptor_index?+?U2?index)
0000?//start_pc?0
0005?//length?5?說明該局部變量從偏移量0開始到0+5?一直被使用
0014?//name_index?指向常量池中的常量?0x14?=>?#20?即this
0015?//descriptor_index?指向常量池中的常量?0x15?=>?#21?即Lcom/insanexs/mess/javap/JavapTest;
0000?//index?0
//method_2
0001?//access_flag?=>0x01?=>ACC_PUBLIC
0016?//name_index?指向常量池中的0x16?=>#22?即??publicMethod
0010?//descriptor_index?指向常量池中的0x10?=>#16?即?()V?表示無參且無返回值
0001?//attribute_count?表示之后有1個(gè)attributes_info
0011?//attribute_name_index?同樣指向常量中的0x11?#17即CODE屬性表
0000?002b?//length?=?43?表示之后43個(gè)字節(jié)為CODE屬性表的內(nèi)容
0000?//max_stack?=?0
0001?//max_locals?=?1
0000?0001?//code_length?=?1
b1?//指令?表示return
0000?//exception_table_length?=?0?無異常表
0002?//attributes_count?=?2
0012?//attribute_name_index?指向常量池中的#18?LineNumberTable
0000?0006?//attribute_length?表示后6個(gè)字節(jié)為LineNumberTable的信息
0001?//表示只有一個(gè)line_number_table
0000?//start_pc?0?對(duì)應(yīng)指令return
0013?//line_number?19??表示return對(duì)應(yīng)的行號(hào)是19
0013?//attribute_name_index?指向常量池中的#19?LocalVariableTable
0000?000c?//attribute_length?表示之后12個(gè)字節(jié)均為?LocalVariableTable?屬性表中的內(nèi)容
0001?//local_variable_table_length?表示有1個(gè)局部變量表
0000?//start_pc?0
0001?//length?1
0014?//name_index?指向常量池中的常量?0x14?=>?#20?即this
0015?//descriptor_index?指向常量池中的常量?0x15?=>?#21?即Lcom/insanexs/mess/javap/JavapTest;
0000?//index?0
//method?3
0004?//access_flag?=>0x04?=>ACC_PROTECTED
0017?//name_index?常量池中#23?即?protectedReturnStrMethod
0018?//descriptor_index?常量池中#24?()Ljava/lang/String;?表示無參,單接返回值類型為String
0001?//attribute_count?表示有一個(gè)attribute_info
0011?//attribute_name_index?同樣指向常量中的0x11?#17即CODE屬性表
0000?002f?//length?=?47?之后47個(gè)字節(jié)均為CODE屬性表的內(nèi)容
0001?//max_stack?=?1
0001?//max_locals?=?1
0000?0005?//code_length?=?5?表示方法含有五個(gè)指令
2a?b4?00?02?b0?//字節(jié)碼指令?分別表示aload_0?getfield?(0002?=>常量池#2)?areturn
0000?//exception_table_length?=?0?表示無異常表
0002?//attributes_count表示有兩個(gè)屬性表
0012?//attribute_name_index?常量池#18?LineNumberTable
0000?0006?//attribute_length?表示后6個(gè)字節(jié)為LineNumberTable的信息
0001?//表示只有一個(gè)line_number_table
0000?//start_pc?0?對(duì)應(yīng)的指令aload_0
0016?//line_number?對(duì)應(yīng)line:22
0013?//attribute_name_index?常量池#19?LocalVariableTable
0000?000c?//attribute_length?表示之后12個(gè)字節(jié)均為?LocalVariableTable?屬性表中的內(nèi)容
0001?//local_variable_table_length?表示有1個(gè)局部變量表
0000?//start_pc?0
0005?//length?5
0014?//name_index?指向常量池#20?即this
0015?//descriptor_index?指向常量池#21?即Lcom/insanexs/mess/javap/JavapTest;
0000?//index?0
//method?4
0022?//access_flag?=>?(0x20?|?0x02)?=>?ACC_SYNCHRONIZED?ACC_PRIVATE
0019?//name_index?常量池中#25?即?privateSynchronizedMethod
001a?//?descriptor_index?常量池中#26?(I)V?接受一個(gè)int參數(shù)?但無返回值
0001?//attribute_count?表示有一個(gè)attribute_info
0011?//attribute_name_index?同樣指向常量中的0x11?#17即CODE屬性表
0000?003e?//length?=?62?之后的62個(gè)字節(jié)均為CODE屬性
0002?//max_stack?=?2
0002?//max_locals?=?2
0000?0006?//code_length?=?6?表示之后6個(gè)字節(jié)均為字節(jié)碼指令
2a?1b?b5?00?03?b1?//分別為aload_0?iload_1?putfield?(0003?=>常量池#3)?return
0000?//exception_table_length?=?0?表示無異常表
0002?//attributes_count表示有兩個(gè)屬性表
0012?//attribute_name_index?常量池#18?LineNumberTable
0000?000a?//attribute_length?表示后10個(gè)字節(jié)為LineNumberTable的信息
0002?//表示有2個(gè)line_number_table
0000?//start_pc?0?對(duì)應(yīng)的指令aload_0
001a?//line_number?對(duì)應(yīng)line:26
0005?//start_pc?5?對(duì)應(yīng)的指令return
001b?//line_number?對(duì)應(yīng)line:27
0013?//attribute_name_index?常量池#19?LocalVariableTable
0000?0016?//attribute_length?=?22?之后22個(gè)字節(jié)均為局部變量表的內(nèi)容
0002?//local_variable_table_length?=?2?表示存在兩個(gè)局部變量表
0000?//start_pc?0
0006?//length?6
0014?//name_index?常量池#20?即this
0015?//descriptor_index?指向常量池#21?即Lcom/insanexs/mess/javap/JavapTest;
0000?//index?0
0000?//start_pc?0
0006?//length?6
001b?//name_index?指向常量池#27?即intArgs
000b?//descriptor_index?指向常量池#11?即I?表示int類型
0001?//index?1
屬性表信息
在解析完方法后,剩下還有一小部分內(nèi)容是屬性表信息?,F(xiàn)在我們對(duì)屬性表的解析過程可以說是輕車熟路了。
未解析的字節(jié)并不多了:
0001?001c?0000?0002?001d
首先attributes_count為0001,說明只有一個(gè)屬性表,so easy!attributes_name_index為001c,指向常量池#28,即SourceFile 用于記錄源文件名稱。
SourceFile的結(jié)構(gòu)如下:
SourceFile_attribute?{
?u2?attribute_name_index;
?u4?attribute_length;
?u2?sourcefile_index;
}
定長的結(jié)構(gòu),最后2個(gè)字節(jié)表示源文件名稱索引,指向常量池。這里為001d,指向常量池中#29,為JavapTest.java。
這樣,我們就完成了所有的字節(jié)碼文件解析。完成的解析結(jié)果和源碼可以私我微信:codeq,免費(fèi)獲??!。
其他問題的小測(cè)試
這里針對(duì)類文件結(jié)構(gòu)學(xué)習(xí)過程中,幾個(gè)疑問做了下測(cè)試。
同步方法和同步塊在類文件結(jié)構(gòu)中的表示有什么不同?
上文我們已經(jīng)測(cè)了同步方法,其是通過方法的access_flag的標(biāo)志位(ACC_SYNCHRONIZED)表示的。但是這種方式對(duì)于同步塊而言已經(jīng)是不行的,那么同步塊是如何實(shí)現(xiàn)同步控制的呢?
測(cè)試代碼類如下:
public?class?SynchronizedTest?{
????public?synchronized?void?synchronizedMethod(){
????????return;
????}
????public?static?synchronized?void?staticSynchronizedMethod(){
????????return;
????}
????public?void?synchronizedCode(){
????????synchronized?(this){
????????????return;
????????}
????}
????public?void?staticSynchronizedCode(){
????????synchronized?(SynchronizedTest.class){
????????????return;
????????}
????}
}
使用javap -verbose -c 反編譯class文件。
截取相關(guān)四個(gè)方法,如下:
??public?synchronized?void?synchronizedMethod();
????descriptor:?()V
????flags:?ACC_PUBLIC,?ACC_SYNCHRONIZED
????Code:
??????stack=0,?locals=1,?args_size=1
?????????0:?return
??????LineNumberTable:
????????line?11:?0
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature
????????????0???????1?????0??this???Lcom/insanexs/mess/javap/SynchronizedTest;
??public?static?synchronized?void?staticSynchronizedMethod();
????descriptor:?()V
????flags:?ACC_PUBLIC,?ACC_STATIC,?ACC_SYNCHRONIZED
????Code:
??????stack=0,?locals=0,?args_size=0
?????????0:?return
??????LineNumberTable:
????????line?15:?0
??public?void?synchronizedCode();
????descriptor:?()V
????flags:?ACC_PUBLIC
????Code:
??????stack=2,?locals=3,?args_size=1
?????????0:?aload_0
?????????1:?dup
?????????2:?astore_1
?????????3:?monitorenter
?????????4:?aload_1
?????????5:?monitorexit
?????????6:?return
?????????7:?astore_2
?????????8:?aload_1
?????????9:?monitorexit
????????10:?aload_2
????????11:?athrow
??????Exception?table:
?????????from????to??target?type
?????????????4?????6?????7???any
?????????????7????10?????7???any
??????LineNumberTable:
????????line?19:?0
????????line?20:?4
????????line?21:?7
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature
????????????0??????12?????0??this???Lcom/insanexs/mess/javap/SynchronizedTest;
??????StackMapTable:?number_of_entries?=?1
????????frame_type?=?255?/*?full_frame?*/
??????????offset_delta?=?7
??????????locals?=?[?class?com/insanexs/mess/javap/SynchronizedTest,?class?java/lang/Object?]
??????????stack?=?[?class?java/lang/Throwable?]
??public?void?staticSynchronizedCode();
????descriptor:?()V
????flags:?ACC_PUBLIC
????Code:
??????stack=2,?locals=3,?args_size=1
?????????0:?ldc???????????#2??????????????????//?class?com/insanexs/mess/javap/SynchronizedTest
?????????2:?dup
?????????3:?astore_1
?????????4:?monitorenter
?????????5:?aload_1
?????????6:?monitorexit
?????????7:?return
?????????8:?astore_2
?????????9:?aload_1
????????10:?monitorexit
????????11:?aload_2
????????12:?athrow
??????Exception?table:
?????????from????to??target?type
?????????????5?????7?????8???any
?????????????8????11?????8???any
??????LineNumberTable:
????????line?25:?0
????????line?26:?5
????????line?27:?8
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature
????????????0??????13?????0??this???Lcom/insanexs/mess/javap/SynchronizedTest;
??????StackMapTable:?number_of_entries?=?1
????????frame_type?=?255?/*?full_frame?*/
??????????offset_delta?=?8
??????????locals?=?[?class?com/insanexs/mess/javap/SynchronizedTest,?class?java/lang/Object?]
??????????stack?=?[?class?java/lang/Throwable?]
從上面可以分析出:無論是類還是實(shí)例的同步方法,都是通過在ACCESS_FLAG中將ACC_SYNCHRONIZED位標(biāo)識(shí)為真實(shí)現(xiàn)的。
而對(duì)于同步塊,則是通過字節(jié)碼指令中添加monitorenter和monitorexit實(shí)現(xiàn)的。而鎖的對(duì)象則是根據(jù)操作數(shù)棧當(dāng)前的對(duì)象所決定。
測(cè)試max_stack
這個(gè)測(cè)試主要是加深對(duì)操作數(shù)棧的理解,設(shè)計(jì)一個(gè)代碼,讓其在類文件結(jié)構(gòu)中的max_stack為2。
滿足上面要求的測(cè)試代碼如下:
public?class?MaxStackTest?{
????public?int?maxStack2Method(){
????????int?var1?=?1;
????????int?var2?=?2;
????????return?var1?+?var2;
????}
}
為什么說此時(shí)操作數(shù)棧最大深度為2,因?yàn)槭紫葀ar1從局部變量表中加載到操作數(shù)棧,此時(shí)操作數(shù)棧的深度為1,接著繼續(xù)從局部變量表中將var2加載到操作數(shù)棧,此時(shí)棧深度為2。而后為了計(jì)算和,操作數(shù)棧彈出var2和var1,深度重回0。所以最大深度為2。
同樣可以拿javap命令反編譯驗(yàn)證:
?public?int?maxStack2Method();
????descriptor:?()I
????flags:?ACC_PUBLIC
????Code:
??????stack=2,?locals=3,?args_size=1
?????????0:?iconst_1
?????????1:?istore_1
?????????2:?iconst_2
?????????3:?istore_2
?????????4:?iload_1
?????????5:?iload_2
?????????6:?iadd
?????????7:?ireturn
??????LineNumberTable:
????????line?11:?0
????????line?12:?2
????????line?13:?4
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature
????????????0???????8?????0??this???Lcom/insanexs/mess/javap/MaxStackTest;
????????????2???????6?????1??var1???I
????????????4???????4?????2??var2???I
}
測(cè)試64位虛擬機(jī)下,int,long,reference分別占幾個(gè)slot
因?yàn)樵?2位虛擬機(jī)下int和reference只占一個(gè)slot,而long和double占兩個(gè)slot,那么這種情況在64位的虛擬機(jī)下是否依舊如此?
測(cè)試代碼如下:
public?class?SlotTest?{
????private?Object?reference;
????public?void?testSlot(){
????????int?i?=0;
????????long?l?=?1L;
????????Object?reference?=?new?Object();
????????int?j?=?1;
????????System.out.println(i?+?","+?l?+?","?+?reference?+?","?+?j);
????}
}
通過javap查看局部變量表,發(fā)現(xiàn)除了long是占2個(gè)slot的,其余的像int和reference都只占1個(gè)slot。說明和32位的情況一致。
仔細(xì)想想,確實(shí)如此。因此在class文件的開頭校驗(yàn)部分只針對(duì)版本號(hào)進(jìn)行了校驗(yàn),并不區(qū)分是32位還是64位,說明二者的編譯規(guī)則應(yīng)該是一致的。
