前言
Java程序具有 " Write Once , Run Anywhere ." 的跨平臺(tái)特性收擦。實(shí)現(xiàn)這樣的目的鼓拧,Java的方案是:半編譯 + 半解釋?zhuān)?.Class + JVM 。
1娜扇、源程序內(nèi)容會(huì)被編譯為.Class文件错沃,.Class文件具有嚴(yán)格規(guī)定如何從中提取信息,可以理解為 “中間碼”雀瓢,約定使用者如何理解文件內(nèi)容
2枢析、理解了程序內(nèi)容,各個(gè)平臺(tái)根據(jù)自身特色不同刃麸,實(shí)現(xiàn)各自的JVM用來(lái)解釋?zhuān)ǚg).Class文件醒叁,變成真正的本地可執(zhí)行指令。
如此實(shí)現(xiàn)了Java跨平臺(tái)的特性泊业。因此把沼,跨平臺(tái)的基礎(chǔ)為.Class,實(shí)現(xiàn)為JVM脱吱。
本文的目的為:讀懂.Class智政,悉知編寫(xiě)的程序代碼在JVM眼中是什么樣子。而在理解了.Class之后箱蝠,對(duì)于理解JVM续捂、理解字節(jié)碼插樁等有進(jìn)一步幫助。
基礎(chǔ)知識(shí)
字節(jié)碼
字節(jié)碼是一種包含執(zhí)行程序宦搬,由數(shù)據(jù)對(duì)組成的二進(jìn)制文件牙瓢,是一種中間碼。一般來(lái)說(shuō)间校,一字節(jié)占用8位矾克,即包含八位的二進(jìn)制。
文章所指的.Class文件為字節(jié)碼文件憔足,因每字節(jié)占8位胁附,故使用16進(jìn)制表示酒繁,易于閱讀,數(shù)值范圍 00 ~ FF (0 ~ 255).
無(wú)符號(hào)數(shù)基本類(lèi)型
無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字控妻、索引引用州袒、數(shù)值量或按照 UTF-8 編碼構(gòu)成字符串。u1弓候、u2郎哭、u4、u8分別代表 1個(gè)字節(jié)菇存、2個(gè)字節(jié)夸研、4個(gè)字節(jié)和8個(gè)字節(jié)的無(wú)符號(hào)數(shù)。
字面量
字面量是一種固定值的表示法依鸥,本身沒(méi)有含義亥至,需要場(chǎng)景來(lái)為它賦予含義,如何理解贱迟?比如 007 沒(méi)有含義抬闯,但是用來(lái)表示詹姆斯·邦德,你就知道007代表一個(gè)很厲害的特工关筒。在程序中,int x = 10杯缺、String s = "10" 讓字面量 10 具有了不同的意義蒸播。
全限定名
將一個(gè)類(lèi)的全限定名是將類(lèi)全名的.全部替換為/,如java.lang.String替換為java/lang/String
描述符
描述符用來(lái)描述字段的數(shù)據(jù)類(lèi)型萍肆、方法的參數(shù)類(lèi)表和返回值袍榆,每種符號(hào)對(duì)應(yīng)不同數(shù)據(jù)類(lèi)型
標(biāo)識(shí)字符 | 含義 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | 對(duì)象類(lèi)型,如String表示為 Ljava/lang/String; |
Class文件
Java文件包含了一個(gè)類(lèi)的所有信息塘揣,以下是一個(gè)Java類(lèi):
import java.io.Serializable;
public class TestClass implements Serializable{
private int m = 123;
private static int x = 10;
private static final int y = 20;
public int increace(){
return m+1;
}
public void m() throws Exception{
// 具體邏輯不寫(xiě)
}
public static String hello(){
return "hello word";
}
}
此Java文件中包雀,所包含的信息有:
- 類(lèi)明為T(mén)estClass并可被外部訪(fǎng)問(wèn),實(shí)現(xiàn)了Serializable接口
- 擁有類(lèi)變量 x和y亲铡,擁有成員變量 m
- 擁有可被外部訪(fǎng)問(wèn)的類(lèi)函數(shù) hello()才写,擁有可被外部訪(fǎng)問(wèn)的成員函數(shù)increace() 和 m()
note: 如無(wú)特殊說(shuō)明,文章所說(shuō).Class文件均由此Java文件編譯得來(lái)
這些信息在被編譯后將在.Class文件中進(jìn)行表達(dá)奖蔓。通過(guò)命令
Javac fileName.java
可將Java文件編譯成對(duì)應(yīng)的.Class文件赞草。.Class文件為字節(jié)碼文件,可借助對(duì)應(yīng)編輯器閱讀吆鹤。
本文使用的編輯器為 “010” 厨疙,Windows 和 Mac 都有, 自行下載疑务。
.Class文件使用字節(jié)碼表達(dá)信息沾凄,各數(shù)據(jù)間緊湊梗醇,不包含任何分隔符,因此整個(gè).Class文件中存儲(chǔ)的內(nèi)容幾乎是全部程序運(yùn)行時(shí)的必要數(shù)據(jù)撒蟀。如何解析字節(jié)碼數(shù)據(jù)叙谨,就需要制定規(guī)則來(lái)解讀,嚴(yán)格遵守牙肝。
.Class 文件風(fēng)格采用類(lèi)似于C語(yǔ)言結(jié)構(gòu)的偽結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)唉俗。可以將.Class文件看成多張表的集合配椭,通過(guò)表索引虫溜,能找到對(duì)應(yīng)的數(shù)據(jù)」筛祝可以理解為衡楞,數(shù)據(jù)存在的相對(duì)位置,決定了它被賦予的涵義敦姻。
.Class文件格式如下表
類(lèi)型 | 名稱(chēng) | 數(shù)量 | 含義 |
---|---|---|---|
u4 | magic | 1 | 魔數(shù)瘾境,用來(lái)確定是否能被虛擬機(jī)接受 |
u2 | minor_version | 1 | 次版本號(hào) |
u2 | major_version | 1 | 主版本號(hào) |
u2 | constant_pool_count | 1 | 常量池?cái)?shù)量 |
cp_info | constant_pool | constant_pool_count-1 | 常量池內(nèi)容 |
u2 | access_flags | 1 | 訪(fǎng)問(wèn)標(biāo)志 |
u2 | this_class | 1 | 類(lèi)索引 |
u2 | super_class | 1 | 父類(lèi)索引 |
u2 | interfaces_count | 1 | 接口數(shù) |
u2 | interfaces | interfaces_count | 接口表集合 |
u2 | fields_count | 1 | 字段數(shù) |
field_info | fields | field_count | 字段表集合 |
u2 | methods_count | 1 | 方法數(shù) |
method_info | methods | methods_count | 方法表集合 |
u2 | attributes_count | 1 | 屬性數(shù) |
attribute_info | attributes | attributes_count | 屬性表結(jié)合 |
有些數(shù)據(jù)信息是定長(zhǎng)的,有些視具體情況而定镰惦,但都會(huì)有相應(yīng)的約束告知具體長(zhǎng)度迷守。各信息對(duì)應(yīng)已在.Class實(shí)例文件圖標(biāo)出,剩下的是逐層去解析類(lèi)信息旺入。
常量池
常量池中主要存放兩大類(lèi):字面量和符號(hào)引用兑凿。符號(hào)引用包括:
- 類(lèi)和接口的全限定名
- 字段的名稱(chēng)和描述符
- 方法的名稱(chēng)和描述符
與C和C++不同,Java代碼編譯后沒(méi)有“連接”的步驟茵瘾,在JVM加載Class文件的時(shí)候進(jìn)行動(dòng)態(tài)連接礼华。.Class文件不會(huì)保存各方法、字段的最終內(nèi)存布局信息拗秘,因?yàn)椴荒芙?jīng)過(guò)運(yùn)行期轉(zhuǎn)換無(wú)法得到真正的內(nèi)存入庫(kù)地址圣絮,無(wú)法被JVM使用。在JVM運(yùn)行時(shí)雕旨,從常量池中拿到對(duì)應(yīng)的符號(hào)引用扮匠,解析、翻譯到具體的內(nèi)存地址中再進(jìn)行使用凡涩,這些信息也就存于JVM的方法區(qū)中餐禁。
常量池所占用長(zhǎng)度不定,需要 0x0008 ~ 0x0009 提供常量數(shù)量統(tǒng)計(jì)突照,再根據(jù)常量池里的具體常量類(lèi)型推算出具體總占用的長(zhǎng)度帮非。
但這比較繁瑣,每一種常量類(lèi)型對(duì)應(yīng)一份表,需要根據(jù)表的不同查閱具體的表結(jié)構(gòu)來(lái)獲取信息末盔。常量類(lèi)型的第一位 u1 表明來(lái)對(duì)應(yīng)常量的表結(jié)構(gòu)筑舅,對(duì)應(yīng)信息如下
類(lèi)型 | 標(biāo)志 | 描述 |
---|---|---|
CONSTANT_Utf8_info | 1 | UTF-8 編碼的字符串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮點(diǎn)型字面量 |
CONSTANT_Long_info | 5 | 長(zhǎng)整型字面量 |
CONSTANT_Double_info | 6 | 雙精度浮點(diǎn)型字面量 |
CONSTANT_Class_info | 7 | 類(lèi)或接口的符號(hào)引用 |
CONSTANT_String_info | 8 | 字符串類(lèi)型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符號(hào)引用 |
CONSTANT_Methodref_info | 10 | 類(lèi)中方法的符號(hào)引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符號(hào)引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的部分符號(hào)引用 |
CONSTANT_MethodHandle_info | 15 | 方法句柄 |
CONSTANT_MethodType_info | 16 | 標(biāo)識(shí)方法類(lèi)型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一個(gè)動(dòng)態(tài)方法調(diào)用點(diǎn) |
除必要外,本文不打算列出各個(gè)對(duì)應(yīng)的表結(jié)構(gòu)陨舱,具體結(jié)構(gòu)可參考 Class類(lèi)文件結(jié)構(gòu)之常量表
這里拋磚引玉翠拣。
第一個(gè)常量類(lèi)型由 0x000A 處標(biāo)出,值為0A游盲,十進(jìn)制為10误墓,查表,類(lèi)型為CONSTANT_Methodref_info(不得不說(shuō)現(xiàn)在的編輯器很強(qiáng)大益缎,沒(méi)有對(duì)應(yīng)功能的話(huà)就只能慢慢查了)谜慌。表中數(shù)據(jù)類(lèi)型為u1、u2莺奔、u2 共占 5 個(gè)字節(jié)(具體表信息和內(nèi)容含義后文再續(xù))欣范。如果是CONSTANT_Utf8_info類(lèi)型,還會(huì)有 length 屬性表明字面量占用字節(jié)長(zhǎng)度令哟,需要加上此長(zhǎng)度恼琼。則第二個(gè)常量類(lèi)型由 0x000F 處標(biāo)出,值為0F屏富,十進(jìn)制為09晴竞,表類(lèi)型為CONSTANT_Fieldref_info。以此類(lèi)推...
這樣一步一步查找對(duì)應(yīng)常量也是比較麻煩的狠半,好在Java內(nèi)置類(lèi)工具——javap可對(duì).Class文件字節(jié)碼進(jìn)行分析颓鲜,通過(guò)命令
javap -verbose fileName
能得到下圖信息 :(僅展示了常量池部分)
常量池?cái)?shù)量值在 0x0008 ~ 0x0009 為 23,轉(zhuǎn)換十進(jìn)制為35典予,表示常量池索引范圍為 1~35。觀察上兩張圖乐严,前者索引從0開(kāi)始瘤袖,后者索引從1開(kāi)始。
若摸不著門(mén)路昂验,常量池的分析著實(shí)讓人頭大捂敌,個(gè)人看來(lái),常量池里的信息是在 “搭積木” 既琴。
本例子中占婉,常量池涉及到的常量類(lèi)型為:
- CONSTANT_Methodref_info
- CONSTANT_Fieldref_info
- CONSTANT_String_info
- CONSTANT_Class_info
- CONSTANT_Interger
- CONSTANT_NameAndType
- CONSTANT_Utf8
暫時(shí)拋開(kāi)具體表結(jié)構(gòu),以上表類(lèi)型結(jié)構(gòu)關(guān)系如示:
上面僅畫(huà)出了當(dāng)前例子涉及到的常量類(lèi)型的組成關(guān)系甫恩,任意類(lèi)型的常量逆济,不斷拆分,最后都會(huì)指向基本類(lèi)型的常量CONSTANT_Utf8,或自身就為基本類(lèi)型如CONSTANT_Interger奖慌∨壮妫可以理解為,
基本常量類(lèi)型CONSTANT_Utf8本身沒(méi)有過(guò)多意義简僧,其它的類(lèi)型為場(chǎng)景建椰,為CONSTANT_Utf8賦予了意義。
CONSTANT_Utf8_info可以算是最基本的類(lèi)型岛马,結(jié)構(gòu)為
// 偽代碼
{
// 常量類(lèi)型
u1 tag;
// 字節(jié)長(zhǎng)度
u2 length;
// UTF-8縮略編碼
bytes[length];
}
在遇到CONSTANT_Utf8_info類(lèi)型的常量時(shí)棉姐,將bytes逐個(gè)按照UTF-8縮略編碼即可得到對(duì)應(yīng)的字面量
類(lèi)級(jí)信息
定義的類(lèi)為
public class TestClass implements Serializable
其中包含的信息為:
- 類(lèi)本身: TestClass
- 訪(fǎng)問(wèn)標(biāo)志:public
- 實(shí)現(xiàn)接口Serializable
- 父類(lèi)為:Object
從.Class文件格式表中,在常量池后僅接著的數(shù)據(jù)啦逆,就是類(lèi)級(jí)數(shù)據(jù)
從 0x0143 ~ 0x014C :
- access_flags(u2): 十六進(jìn)制值為 0x0021
- this_class(u2): 十進(jìn)制值為5伞矩,指向常量池第5個(gè)常量, 類(lèi)型為CONSTANT_Class_info蹦浦,類(lèi)為 TestClass
- super_class(u2): 十進(jìn)制值為5扭吁,指向第6個(gè)常量,類(lèi)型為CONSTANT_Class_info盲镶,類(lèi)為 java/lang/Object
- interface_count(u2): 實(shí)現(xiàn)接口數(shù)量 1 個(gè)
- interface[0] : 指向常量池第7個(gè)常量侥袜,類(lèi)型為CONSTANT_Class_info,接口名誒 java/io/Serializable
CONSTANT_Class_info 常量表結(jié)構(gòu)如下
// 偽代碼
{
// 常量類(lèi)型
u1 tag ;
// 指向常量池偏移量為name_index溉贿,類(lèi)型為CONSTANT_Utf8_info類(lèi)型的索引呼畸,
//代表類(lèi)或接口的權(quán)限定名
u2 name_index;
}
與之前所說(shuō)常量池里在搭積木的說(shuō)法一致狸演,后面涉及到的常量池里的類(lèi)型依然如此。
訪(fǎng)問(wèn)標(biāo)志使用標(biāo)志位來(lái)表示,各個(gè)標(biāo)志含義如表
標(biāo)志名稱(chēng) | 標(biāo)志值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否為public類(lèi)型 |
ACC_FINAL | 0x0010 | 是否為final黄刚,僅類(lèi)能聲明 |
ACC_SUPER | 0x0020 | 是否允許使用invokespecial字節(jié)碼指令新語(yǔ)意,在JDK 1.0.2改變過(guò) 需要區(qū)分 |
ACC_INTERFACE | 0x0200 | 標(biāo)識(shí)這是一個(gè)接口 |
ACC_ABSTRACT | 0x0400 | 是否為abstract類(lèi)型 |
ACC_SYNTHETIC | 0x1000 | 表示這個(gè)類(lèi)并非由用戶(hù)代碼產(chǎn)生 |
ACC_ANNOTATION | 0x2000 | 標(biāo)識(shí)這是一個(gè)注解 |
ACC_ENUM | 0x4000 | 標(biāo)識(shí)這是一個(gè)枚舉 |
當(dāng)前情況為 0x0001 | 0x0020 = 0x0021
attribute(屬性表)
屬性表比較特殊脖捻,.Class文件弥搞、字段表、方法表等都可以攜帶自己的屬性表集合抢蚀,用來(lái)描述專(zhuān)有的場(chǎng)景镀层,也因此將此表做前置說(shuō)明。
屬性表的特點(diǎn)為:
- 規(guī)則較寬松皿曲,不要求嚴(yán)格的順序唱逢、長(zhǎng)度、內(nèi)容
- 只要不與已有屬性表重復(fù)屋休,任何編譯器都可以向?qū)傩员碇袑?xiě)入自定義的屬性信息坞古,JVM會(huì)忽略掉不認(rèn)識(shí)的屬性。
屬性表結(jié)構(gòu)為
// 偽代碼
{
// 指向常量池類(lèi)型為CONSTANT_Utf8_info的常量劫樟,代表屬性名
u2 attribute_name_index ;
// 屬性表info占用長(zhǎng)度
u4 attribute_length;
// 這需要具體實(shí)現(xiàn)的結(jié)構(gòu)痪枫,長(zhǎng)度為attribute_length
Info info;
}
因此一個(gè)屬性表的長(zhǎng)度為 u2 + u4 + attribute_length织堂。
Java與定義來(lái)很多屬性表,文章檢出涉及到的做后續(xù)說(shuō)明听怕,其它在實(shí)際需要時(shí)自行查閱
屬性名稱(chēng) | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節(jié)碼指令 |
ConstantValue | 字段表 | final 關(guān)鍵字定義的常量值 |
Exceptions | 方法表 | 方法拋出的異常 |
LineNumberTable | Code屬性 | Java源碼的行號(hào)與字節(jié)碼指令對(duì)應(yīng)的關(guān)系 |
SourceFile | 類(lèi)文件 | 記錄源文件名稱(chēng) |
字段表與方法表攜帶的屬性表暫未涉及捧挺,當(dāng)前節(jié)點(diǎn)涉及到類(lèi)型為SourceFile的.Class攜帶的屬性表。
范圍為 0x025A ~ 0x0262 共占 u2 + u4 + attibute_length = 8 字節(jié)
SourceFile屬性結(jié)構(gòu)如下
偽代碼
{
// 指向常量池類(lèi)型為CONSTANT_Utf8_info的常量尿瞭,代表屬性名
u2 attribute_name_index ;
// 屬性表內(nèi)容占用長(zhǎng)度
u4 attribute_length;
// 指向常量池類(lèi)型為CONSTANT_Utf8_info的常量闽烙,代表源文件名
u2 sourcefile_index;
}
因此通過(guò)此SourceFile屬性表,得知源文件名為T(mén)estClass.java
字段表
查閱.Class文件格式表声搁,接口表之后黑竞,就是字段數(shù)量已經(jīng)字段數(shù)量表
從 0x014D ~ 0x016E , 其中 0x014D ~ 0x014E 表示字段數(shù)疏旨, 值為 0x0003很魂,表示字段數(shù)為3,隨便就是緊挨著的字段表檐涝。字段表結(jié)構(gòu)如下
// 偽代碼
{
// 訪(fǎng)問(wèn)標(biāo)志
u2 access_flags
// 指向常量池類(lèi)型為CONSTANT_Utf8_info的常量遏匆,表示字段名
u2 name_index
// 指向常量池類(lèi)型為CONSTANT_Utf8_info的常量,用描述符表示
// 字段類(lèi)型
u2 descriptor_index
// 屬性表數(shù)量
attributes_count
// 屬性表內(nèi)容
attribuite_info
}
字段表和.Class一樣谁榜,能攜帶自己的屬性表處理特殊場(chǎng)景幅聘,attribuite_info是非必須的。當(dāng)attributes_count的值為0時(shí)窃植, 說(shuō)明無(wú)需attribuite_info帝蒿。字段也擁有訪(fǎng)問(wèn)標(biāo)志來(lái)對(duì)字段做進(jìn)一步約束。字段名則用name_index指向常量池的常量來(lái)表示巷怜,字段類(lèi)型則用描述符來(lái)表示葛超, 比如 Int 表示為 I (忘了往前看基礎(chǔ)知識(shí))。
當(dāng)前例子定義的字段如下:
private int m = 123;
private static int x = 10;
private static final int y = 20;
定義了成員變量 m 和 類(lèi)變量 x 延塑,y绣张。 舉例 y 來(lái)看
位置為 0x015F ~ 0x016E,其中:
- 訪(fǎng)問(wèn)標(biāo)志: 0x001A
- 字段名索引: 0x000B关带,十進(jìn)制值為11侥涵,指向常量池第11個(gè)常量,為y
- 描述符索引為:0x0009豫缨,指向常量池第9個(gè)常量,為I
- 屬性表數(shù)為: 0x0001端朵,數(shù)量為1
- 屬性表總占用長(zhǎng)度為:u2 + u4 + 2字節(jié) 共10位好芭,即 0x0167 ~ 0x016E
字段訪(fǎng)問(wèn)標(biāo)志位含義如表:
標(biāo)志名稱(chēng) | 標(biāo)志位 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否為public |
ACC_PRIVATE | 0x0002 | 是否為private |
ACC_PROTECTED | 0x0004 | 是否為protected |
ACC_STATIC | 0x0008 | 是否為static |
ACC_FINAL | 0x0010 | 是否為final |
ACC_VOLATILE | 0x0040 | 是否為volatile |
ACC_TRANSIENT | 0x0080 | 是否為transient |
ACC_SYNTHETIC | 0x1000 | 是否是由編譯器自動(dòng)產(chǎn)生 |
ACC_ENUM | 0x4000 | 字段是否enum |
當(dāng)前為 private static final ,即 0x0001 | 0x0008 | 0x0010 冲呢, 為 0x001A舍败。
通過(guò)訪(fǎng)問(wèn)標(biāo)志、字段索引、描述符信息邻薯,就可以拿到
-> private static final int y 這一信息裙戏。
對(duì)于使用 final和static修飾的并且時(shí)基本數(shù)據(jù)類(lèi)型的變量,會(huì)使用屬性表ConstantVulue來(lái)進(jìn)行賦值厕诡。屬性表 ConstantVulue 除了約定的基本數(shù)據(jù)外累榜,還有類(lèi)型為 u2 的ConstantValue_index 索引來(lái)表示指向常量池中的常量用來(lái)初始化數(shù)據(jù)。值位于 0x016D ~ 0x016E灵嫌,為 0x000D壹罚,指向的常量表索引13處的值為整型的20;
而實(shí)例中的變量m寿羞,類(lèi)變量x則在成員初始函數(shù)猖凛、類(lèi)初始函數(shù)中進(jìn)行賦值,下文做說(shuō)明绪穆。
方法表
字段表之后辨泳,緊挨著的是方法數(shù)量與方法表集合
范圍為 0x016F ~ 0x0258,方法數(shù)值為 0x0005 共 5 個(gè)方法玖院。除了示例自定義的 increace() 菠红,m() 和 hello() 外,還有實(shí)例構(gòu)造方法<init>()v司恳,類(lèi)構(gòu)造器<clinit>()方法途乃。
方法表結(jié)構(gòu)為:
偽代碼
{
// 訪(fǎng)問(wèn)標(biāo)志
u2 access_flags;
// 方法名索引,指向常量池類(lèi)型為CONSTANT_Utf8_info的常量
u2 name_index;
// 方法返回值描述符索引扔傅,指向常量池類(lèi)型為CONSTANT_Utf8_info的常量
u2 descriptor_index耍共;
// 屬性表數(shù)量
u2 attributes_count;
// 屬性表內(nèi)容
Info info;
}
方法也可以攜帶屬性表來(lái)描述專(zhuān)有場(chǎng)景。通過(guò)上述結(jié)構(gòu)以及具體字節(jié)碼猎塞,可以推算出方法所包含的內(nèi)容试读。其中,訪(fǎng)問(wèn)標(biāo)志含義如表:
標(biāo)志名稱(chēng) | 標(biāo)志值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否為public |
ACC_PRIVATE | 0x0002 | 是否為private |
ACC_PROTECTED | 0x0004 | 是否為protected |
ACC_STATIC | 0x0008 | 是否為static |
ACC_FINAL | 0x0010 | 是否為final |
ACC_SYNCHRONIZED | 0x0020 | 是否為synchronized |
ACC_BRIDGE | 0x0040 | 是否有編譯器產(chǎn)生的橋接方法 |
ACC_VARARGS | 0x0080 | 是否接受不定參數(shù) |
ACC_NATIVE | 0x0100 | 是否為native |
ACC_ABSTRACT | 0x0400 | 是否為abstract |
ACC_STRICTFP | 0x0800 | 是否為strictfp |
ACC_SYNTHETIC | 0x1000 | 是否由編譯器自動(dòng)產(chǎn)生 |
取構(gòu)造實(shí)例方法方法<init>()做示例來(lái)看:
信息為:
- access_flags: 0x0001 荠耽,為public
- name_index: 0x000E钩骇,為14,對(duì)應(yīng)常量池得到 <init>
- descriptor_index: 0x000F铝量,為15倘屹,對(duì)應(yīng)常量池得到 ()V
- attributes_count: 0x0001,為1慢叨,屬性表數(shù)量為1
可以根據(jù)信息反推得到函數(shù)信息纽匙,實(shí)例化函數(shù)被表達(dá)為 public <init>()V,依次為訪(fǎng)問(wèn)標(biāo)志位拍谐、函數(shù)名烛缔、返回值馏段。
一個(gè)函數(shù)方法,更重要的如何表述它所提供的功能践瓷。本質(zhì)上來(lái)說(shuō)院喜,函數(shù)體里所有的代碼段都是在進(jìn)行運(yùn)算操作,因此晕翠,只要將函數(shù)體里的代碼段轉(zhuǎn)換為字節(jié)碼指令即可喷舀,函數(shù)執(zhí)行時(shí)根據(jù)執(zhí)行即可,再次之上再幾率一下關(guān)鍵信息崖面,就能得出函數(shù)執(zhí)行元咙、棧深度、局部變量數(shù)巫员、字節(jié)碼占用文件大小庶香。
在attributes_count之后,從 0x0179 ~ 0x01A5 是屬性表包含的內(nèi)容简识。根據(jù)之前所說(shuō)的屬性表約定的格式 0x0179 ~ 0x0180 位置值為 0x0010赶掖,十進(jìn)制為16,查找啊常量池知屬性表為Code類(lèi)型七扰。
Code類(lèi)型屬性表結(jié)構(gòu)如下:
// 偽代碼
{
// 屬性表名稱(chēng)索引奢赂,指向常量表類(lèi)型為CONSTANT_Utf8_info的常量
u2 attribute_name_index;
// 屬性表長(zhǎng)度
u4 attribute_length;
// 棧深
u2 max_stack;
// 局部變量數(shù)
u2 max_locals;
// 字節(jié)碼指令長(zhǎng)度
u4 code_length;
// 字節(jié)碼指令
u1 code code_length;
// 異常表數(shù)量
u2 exception_table_length;
// 異常表
exception_info exception_table;
// 屬性表數(shù)量
u2 attributes_count;
// 屬性表
attribute_info attributes;
}
獲取方法信息不僅能直接通過(guò)閱讀字節(jié)碼文件,通過(guò)
javap -verbose className
也可以拿到颈走,一起貼了
紅圈為字節(jié)碼指令集膳灶,黃圈為每一條字節(jié)碼指令,綠圈為Code屬性表的基本信息立由,藍(lán)圈為javap工具解析出的實(shí)例函數(shù)信息轧钓。
首先,最大棧深為2锐膜,在函數(shù)執(zhí)行的任意時(shí)刻都不會(huì)超過(guò)這個(gè)操作數(shù)棧深度的最大值毕箍;然后局部變量數(shù)為1,表示局部變量表所需的存儲(chǔ)空間道盏,單位為Slot而柑,此單位是JVM為局部變量分配內(nèi)存所使用的最小單位。藍(lán)圈里還有args_sige表示方法接受的參數(shù)數(shù)荷逞,這里為1媒咳,也就是 this。最后就是函數(shù)代碼塊里轉(zhuǎn)成的字節(jié)碼指令里种远。
字節(jié)碼指令不在文章的討論范圍內(nèi)涩澡,不妨簡(jiǎn)單了解。
字節(jié)碼指令代表著某種特定操作院促,由一個(gè)字節(jié)長(zhǎng)度代表其操作含義筏养,后面可以跟隨0到多個(gè)所需操作數(shù)。
本例子中的初始化函數(shù)被翻譯成了:
2A B7 00 01 2A 10 7B B5 00 02 B1
也就是上圖藍(lán)圈處的:
{
// 將 this 入棧
0: aload_0
// 喚醒父類(lèi)實(shí)例化函數(shù)
1: invokespecial #1
4: aload_0
// 將 123 入棧
5: bitpush 123
// 訪(fǎng)問(wèn)字段 m常拓,將123存入
7: putfield #2
// 方法返回
10: return
}
這里只說(shuō)明 putfield #2渐溶,對(duì)應(yīng)的字節(jié)碼為 B5 00 02,其中 B5 代表操作執(zhí)行 00 02 為操作所需參數(shù)弄抬,值為 0x0002茎辐,表示指向常量池類(lèi)型為 CONSTANT_Fieldref_info 的常量。
CONSTANT_Fieldref_info 結(jié)構(gòu)為
// 偽代碼
CONSTANT_Fieldref_info
{
u1 tag;
// 指向常量池類(lèi)型為CONSTANT_ClassInfo_info的常量
// 代表字段所屬類(lèi)
u2 class_index;
// 指向常量池類(lèi)型為CONSTANT_NameAndType_info的常量
// 代表字段名和類(lèi)型
u2 name_and_type_index;
}
CONSTANT_ClassInfo_info
{
u1 tag;
// 指向常量池尾CONSTANT_Utf8_info的常量
// 代表類(lèi)名
u2 name_index;
}
CONSTANT_NameAndType_info
{
u1 tag;
// 指向常量池尾CONSTANT_Utf8_info的常量
// 代表 名稱(chēng)
u2 name_index;
// 指向常量池尾CONSTANT_Utf8_info的常量
// 代表 所屬類(lèi)型
u2 descriptor_index;
}
當(dāng)前為指向第二個(gè)常量掂恕,對(duì)照信息:
能知道字節(jié)碼操作 B5 00 02 是將棧中的 123 給 TestClass.m 進(jìn)行賦值 拖陆,也就是例子中定義的
private int m = 123 的賦值操作。
而 x 的賦值則在類(lèi)構(gòu)造器<cinit>中進(jìn)行懊亡,inicrea()和m()函數(shù)也可以用相同的方式進(jìn)行分析依啰。不再陳述,點(diǎn)到為止店枣。
總結(jié)
至此速警,了解.Class文件的如何,通過(guò).Class文件格式表可以解析出文件內(nèi)容的基本信息鸯两。.Class文件可以看成多張表的集合闷旧,根據(jù)表制定的規(guī)則,順藤摸瓜钧唐,自然能找出對(duì)應(yīng)信息忙灼。.Class文件在如何表達(dá)信息上不難理解,難的是有耐心去縷清這些瑣碎的索引關(guān)系钝侠,尤其常量池和屬性表部分该园。屬性表部分則提供了足夠的發(fā)揮空間,根據(jù)場(chǎng)景提供更多內(nèi)容机错。
文章僅了解了解析.Class文件的基本規(guī)則爬范,更進(jìn)一步的解析規(guī)則感興趣或需要時(shí)再了解即可,方法不變弱匪。
參考
《深入理解Java虛擬機(jī)》 —— 第6章
深入理解JVM字節(jié)碼執(zhí)行引擎
java中class文件的意義是什么青瀑?
java語(yǔ)言為什么可以跨平臺(tái)