引導(dǎo)
Class文件的基本結(jié)構(gòu)
Class文件的常量池
Class文件的訪問標(biāo)志,類索引,父類索引,接口索引
Class文件的字段和方法
Class文件的基本結(jié)構(gòu)
我們知道,編寫的 .java 源文件會(huì)被 java 編譯器編譯成 .class 文件,它是由字節(jié)組成的文件,又叫字節(jié)碼文件。
那你知道 class 文件里面都存儲(chǔ)了哪些數(shù)據(jù)嗎?這里我繪制了 class 文件的 UML 圖和基本組成結(jié)構(gòu)。


魔數(shù)
所有 java 編譯器編譯的 .class 文件的前4個(gè)字節(jié)都是 0xCAFEBABE,被稱為魔數(shù)(magic)。
它的作用在于,JVM 在嘗試加載某個(gè)文件的時(shí)候,會(huì)先讀取文件的前4個(gè)字節(jié),判斷是否是 0xCAFEBABE;如果是,則 JVM 會(huì)將此文件作為 class 文件來加載并使用。
版本號(hào)
版本號(hào)表示當(dāng)前 class 文件被哪個(gè)版本的 java 編譯器編譯而成。
- 副版本號(hào)(minor_version),占用第 5、6 兩個(gè)字節(jié);
- 主版本號(hào)(major_version),占用第 7、8 兩個(gè)字節(jié)。
JDK1.0 的主版本號(hào)(major_version) 為 45,以后的每個(gè)新主版本(major_version)都會(huì)在原先版本的基礎(chǔ)上加1。
JVM 在加載 class 文件的時(shí)候,會(huì)讀取其主版本號(hào)(major_version),然后比較主版本號(hào)(major_version) 和 JVM 本身的版本號(hào),如果 JVM 本身的版本號(hào)小于 class 文件的主版本號(hào)(major_version),JVM 會(huì)認(rèn)為加載不了這個(gè) class 文件,并拋出 java.lang.UnsupportedClassVersionError。
舉個(gè)栗子,這里我用 java11 編譯生成 .class 文件,然后使用 java8 運(yùn)行。

常量池計(jì)數(shù)器
常量池計(jì)數(shù)器(constant_pool_count)描述著整個(gè) class 文件的字面量數(shù)量。
常量池?cái)?shù)據(jù)區(qū)
常量池(constant_pool)是由若干 cp_info 的結(jié)構(gòu)體組成。它包含 class 文件結(jié)構(gòu)及其子結(jié)構(gòu)中引用的所有字符串常量、 類或接口名、字段名和其它常量。
常量池(constant_pool)數(shù)組長度由常量池計(jì)數(shù)器(constant_pool_count)指定,它們之間滿足如下關(guān)系。
// 常量池計(jì)數(shù)器 = 常量池?cái)?shù)組長度 + 1
constant_pool_count = constant_pool.length + 1
常量池(constant_pool)數(shù)組的索引范圍是 [1, constant_pool_count ? 1],這里的索引不是從 0 開始的。
將索引為 0 的常量池項(xiàng)空出來是為了滿足特定情況下指向常量池索引的數(shù)據(jù)不引用任何一個(gè)常量池項(xiàng),這種情況下可以將常量池索引設(shè)置為0,比如 java.lang.Object 的父類索引(super_class)。
訪問標(biāo)志
訪問標(biāo)志(access_flags)是一種掩碼標(biāo)志,用于表示某個(gè)類或者接口的訪問權(quán)限及基礎(chǔ)屬性。
一些常見的值以及其含義如下圖所示。

-
ACC_INTERFACE表示是接口。如果一個(gè) class 文件有ACC_INTERFACE標(biāo)志,那么也得設(shè)置ACC_ABSTRACT;并且不能設(shè)置為ACC_FINAL、ACC_SUPER、ACC_ENUM; - 注解類型必須帶有
ACC_ANNOTATION標(biāo)志,如果設(shè)置了ACC_ANNOTATION標(biāo)志,ACC_INTERFACE也必須同時(shí)被設(shè)置; -
ACC_SUPER標(biāo)志用于確定該 class 文件里面的invokespecial指令使用的是哪一種執(zhí)行語義。目前 java 編譯器應(yīng)該都會(huì)設(shè)置這個(gè)標(biāo)志,ACC_SUPER 是為了兼容舊編譯器編譯 class 文件而存在的;
類索引
類索引(this_class)表示這個(gè) class 文件所定義的類或接口。
類索引(this_class) 的值必須是常量池(constant_pool) 數(shù)組中的一個(gè)有效索引值,且常量池(constant_pool)數(shù)組在此索引處的項(xiàng)必須是 CONSTANT_Class_info 類型的。
父類索引
父類索引(super_class)表示這個(gè) class 文件所定義類或接口的直接父類。
- 對(duì)于非
java.lang.Object的類,父類索引(super_class)的值必須是常量池(constant_pool)數(shù)組中的一個(gè)有效索引值,且常量池(constant_pool)數(shù)組在此索引處的項(xiàng)必須為CONSTANT_Class_info結(jié)構(gòu); - 對(duì)于
java.lang.Object類,父類索引(super_class)的值一定是 0; - 對(duì)于接口,父類索引(super_class)的值必須是常量池(constant_pool)數(shù)組中的一個(gè)有效索引值,且常量池(constant_pool)數(shù)組在此索引處的項(xiàng)必須是代表
java.lang.Object類的CONSTANT_Class_info結(jié)構(gòu);
類的直接父類以及所有間接父類的訪問標(biāo)志(access_flag)中都不能帶有 ACC_FINAL 標(biāo)記。
接口計(jì)數(shù)器
接口計(jì)數(shù)器(interfaces_count)表示當(dāng)前類或接口的直接父接口數(shù)量。
接口信息數(shù)據(jù)區(qū)
接口信息(interfaces)是一個(gè)索引數(shù)組,表示當(dāng)前類或接口的直接父接口信息。
接口信息(interfaces)數(shù)組的長度為接口計(jì)數(shù)器(interfaces_count)的值。數(shù)組中每一項(xiàng)必須是常量池(constant_pool)數(shù)組中的一個(gè)有效索引值,且常量池(constant_pool)數(shù)組在此索引處的項(xiàng)必須為 CONSTANT_Class_info 結(jié)構(gòu)。
數(shù)組表示的接口順序和對(duì)應(yīng)的源文件中給定的接口順序(從左至右)一樣,即第一個(gè)元素對(duì)應(yīng)的是源文件中最左邊的接口。
字段計(jì)數(shù)器
字段計(jì)數(shù)器(fields_count)表示當(dāng)前 class 文件聲明的所有字段數(shù)量,不包括從父類或父接口繼承的字段。
字段信息數(shù)據(jù)區(qū)
字段表(fields)表示當(dāng)前 class 文件聲明的所有字段,不包括從父類或父接口繼承的方法。
字段表(fiels)的長度為字段計(jì)數(shù)器(field_count)的值。數(shù)組中每一項(xiàng)都必須是 field_info 結(jié)構(gòu),表示當(dāng)前 class 文件中某個(gè)字段的完整描述。
方法計(jì)數(shù)器
方法計(jì)數(shù)器(methods_count)表示當(dāng)前 class 文件聲明的方法數(shù)量,不包括從父類或父接口繼承的方法。
方法信息數(shù)據(jù)區(qū)
方法表(methods)表示當(dāng)前 class 文件聲明的所有方法,不包括從父類或父接口繼承的字段。
方法表(methods)的長度為方法計(jì)數(shù)器(method_count)的值。數(shù)組中每一項(xiàng)都必須是 method_info 結(jié)構(gòu),表示當(dāng)前 class 文件中某個(gè)方法的完整描述。
如果方法表(methods)中某一項(xiàng)的 access_flags 既沒有設(shè)置 ACC_NATIVE 標(biāo)志,也沒有設(shè)置 ACC_ABSTRACT 標(biāo)志,那么它所對(duì)應(yīng)的方法體就應(yīng)當(dāng)可以被 JVM 直接從當(dāng)前類加載,而不需要引用其它類。
屬性計(jì)數(shù)器
屬性計(jì)數(shù)器(attributes_count)表示當(dāng)前 class 文件的所有屬性數(shù)量。
屬性信息數(shù)據(jù)區(qū)
屬性表(attributes)表示當(dāng)前 class 文件的所有屬性。
屬性表(attributes)的長度為屬性計(jì)數(shù)器(attribute_count)的值。表中每一項(xiàng)都必須是 attribute_info 結(jié)構(gòu),描述某個(gè)屬性的完整信息。
Java 7 規(guī)范里,class 文件結(jié)構(gòu)中的屬性表(attributes)可以包括下列定義的屬性:InnerClasses 、 EnclosingMethod、Synthetic、Signature、SourceFile、SourceDebugExtension、Deprecated、RuntimeVisibleAnnotations 、RuntimeInvisibleAnnotations 以及 BootstrapMethods 屬性。
- 對(duì)于支持 class 文件主版本號(hào)(major_version)為 49.0 或更高的 JVM,必須正確識(shí)別并讀取
Signature、RuntimeVisibleAnnotations和RuntimeInvisibleAnnotations; - 對(duì)于支持 class 文件主版本號(hào)(major_version)為 51.0 或更高的 JVM,必須正確識(shí)別并讀取
BootstrapMethods。
Java 7 規(guī)范要求任一 JVM 可以自動(dòng)忽略 class 文件中它不可識(shí)別的屬性項(xiàng)。