JVM 眼中的 .Class 文件

前言

Java程序具有 " Write Once , Run Anywhere ." 的跨平臺(tái)特性收擦。實(shí)現(xiàn)這樣的目的鼓拧,Java的方案是:半編譯 + 半解釋?zhuān)?.Class + JVM 。

Java跨平臺(tái)原理.png

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文件中包雀,所包含的信息有:

  1. 類(lèi)明為T(mén)estClass并可被外部訪(fǎng)問(wèn),實(shí)現(xiàn)了Serializable接口
  2. 擁有類(lèi)變量 x和y亲铡,擁有成員變量 m
  3. 擁有可被外部訪(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é)碼實(shí)例文件.png

.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ū)中餐禁。

常量池位置.jpg

常量池所占用長(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è)常量.png

第一個(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

能得到下圖信息 :(僅展示了常量池部分)


javap分析常量池.png

常量池?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)系如示:


常量池常量類(lèi)型結(jié)構(gòu).png

上面僅畫(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ù)


類(lèi)級(jí)信息.png

從 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)為:

  1. 規(guī)則較寬松皿曲,不要求嚴(yán)格的順序唱逢、長(zhǎng)度、內(nèi)容
  2. 只要不與已有屬性表重復(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攜帶的屬性表。


class攜帶屬性表示例.png

范圍為 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ù)量表


字節(jié)碼字段表.jpeg

從 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)看

字段Y.png

位置為 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ù)量與方法表集合


方法表集合.png

范圍為 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)看:


實(shí)例化方法表.png

信息為:

  • 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

也可以拿到颈走,一起貼了


Code信息.png

紅圈為字節(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ì)照信息:


m初始化.jpg

能知道字節(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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市萧诫,隨后出現(xiàn)的幾起案子斥难,更是在濱河造成了極大的恐慌,老刑警劉巖帘饶,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哑诊,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡及刻,警方通過(guò)查閱死者的電腦和手機(jī)镀裤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)竞阐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人暑劝,你說(shuō)我怎么就攤上這事骆莹。” “怎么了担猛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵幕垦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我傅联,道長(zhǎng)先改,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任蒸走,我火速辦了婚禮仇奶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘比驻。我一直安慰自己猜嘱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布嫁艇。 她就那樣靜靜地躺著朗伶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪步咪。 梳的紋絲不亂的頭發(fā)上论皆,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音猾漫,去河邊找鬼点晴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛悯周,可吹牛的內(nèi)容都是我干的粒督。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼禽翼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼屠橄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起闰挡,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤锐墙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后长酗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體溪北,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了之拨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茉继。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蚀乔,靈堂內(nèi)的尸體忽然破棺而出馒疹,到底是詐尸還是另有隱情,我是刑警寧澤乙墙,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站生均,受9級(jí)特大地震影響听想,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜马胧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一汉买、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧佩脊,春花似錦蛙粘、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至歇盼,卻和暖如春舔痕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背豹缀。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工伯复, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人邢笙。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓啸如,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親氮惯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子叮雳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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