Class文件的基本結(jié)構(gòu)

引導(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)。

class文件UML圖.png
class文件基本結(jié)構(gòu).png

魔數(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 編譯器編譯而成。

  1. 副版本號(hào)(minor_version),占用第 5、6 兩個(gè)字節(jié);
  2. 主版本號(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)行。

UnsupportedClassVersionError.png

常量池計(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ǔ)屬性。

一些常見的值以及其含義如下圖所示。

access_flags.png
  1. ACC_INTERFACE 表示是接口。如果一個(gè) class 文件有 ACC_INTERFACE 標(biāo)志,那么也得設(shè)置 ACC_ABSTRACT;并且不能設(shè)置為 ACC_FINAL、ACC_SUPER、ACC_ENUM;
  2. 注解類型必須帶有 ACC_ANNOTATION 標(biāo)志,如果設(shè)置了 ACC_ANNOTATION 標(biāo)志,ACC_INTERFACE 也必須同時(shí)被設(shè)置;
  3. 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 文件所定義類或接口的直接父類。

  1. 對(duì)于非 java.lang.Object 的類,父類索引(super_class)的值必須是常量池(constant_pool)數(shù)組中的一個(gè)有效索引值,且常量池(constant_pool)數(shù)組在此索引處的項(xiàng)必須為 CONSTANT_Class_info 結(jié)構(gòu);
  2. 對(duì)于 java.lang.Object 類,父類索引(super_class)的值一定是 0;
  3. 對(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 、 EnclosingMethodSynthetic、Signature、SourceFile、SourceDebugExtension、DeprecatedRuntimeVisibleAnnotations 、RuntimeInvisibleAnnotations 以及 BootstrapMethods 屬性。

  1. 對(duì)于支持 class 文件主版本號(hào)(major_version)為 49.0 或更高的 JVM,必須正確識(shí)別并讀取 Signature、RuntimeVisibleAnnotationsRuntimeInvisibleAnnotations;
  2. 對(duì)于支持 class 文件主版本號(hào)(major_version)為 51.0 或更高的 JVM,必須正確識(shí)別并讀取 BootstrapMethods。

Java 7 規(guī)范要求任一 JVM 可以自動(dòng)忽略 class 文件中它不可識(shí)別的屬性項(xiàng)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容