Class 類文件結(jié)構(gòu)
點擊上方藍色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|? Zzzkis
來源 |? urlify.cn/yuAJJj
一,概述
我們知道,Java 具有跨平臺性,其實現(xiàn)基礎(chǔ)就是虛擬機和字節(jié)碼存儲格式。Java 虛擬機不與 Java 語言綁定,只與 Class 文件所關(guān)聯(lián)。Java 虛擬機作為一個通用的、與機器無關(guān)的執(zhí)行平臺,任何語言都可以將 Java 虛擬機作為它們的運行基礎(chǔ),以 Class 文件作為它們產(chǎn)品的交付媒介。
Class 文件是一組以 8 個字節(jié)為基礎(chǔ)單位的二進制流,各個數(shù)據(jù)項目嚴格按照順序緊湊地排列在文件之中,中間沒有添加任何分隔符,這使得整個 Class 文件中存儲的內(nèi)容幾乎全部是程序運行的必要數(shù)據(jù)。當(dāng)遇到需占用 8 個字節(jié)以上空間的數(shù)據(jù)項時,則會按照高位在前的方式分割成若干個 8 個字節(jié)進行存儲。
Class 文件中有兩種數(shù)據(jù)類型,分別是無符合數(shù)和表:
無符號數(shù)屬于基本數(shù)據(jù)類型,以 u1、u2、u4、u8 來分別代表 1 個字節(jié)、2 個字節(jié)、4 個字節(jié)和 8 個字節(jié)的無符號數(shù),無符號數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或 UTF-8 編碼構(gòu)成字符串值
表是由多個無符號數(shù)或其他表作為數(shù)據(jù)項構(gòu)成的復(fù)合數(shù)據(jù)類型,一般以 _info 結(jié)尾。表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個 Class 文件本質(zhì)上也可以視作是一張表
無論是無符號數(shù)還是表,當(dāng)需要描述同一類型但數(shù)量不定的多個數(shù)據(jù)時,經(jīng)常會使用一個前置的容量計數(shù)器加若干個連續(xù)的數(shù)據(jù)項的形式,這時候稱這一系列連續(xù)的某一類型的數(shù)據(jù)為某一類型的集合。
下面是 Class 文件格式:
| 類型 | 名稱 | 數(shù)量 |
| u4 | magic | 1 |
| u2 | minor_version | 1 |
| u2 | major_version | 1 |
| u2 | constant_pool_count | 1 |
| cp_info | constant_pool | constant_pool_count - 1 |
| u2 | access_flags | 1 |
| u2 | this_class | 1 |
| u2 | super_class | 1 |
| u2 | interfaces_count | 1 |
| u2 | interfaces | interfaces_count |
| u2 | fields_count | 1 |
| field_info | fields | fields_count |
| u2 | methods_count | 1 |
| method_info | methods | methods_count |
| u2 | attribute_count | 1 |
| attribute_info | attributes | attributes_count |
二,魔數(shù)和 Class 文件版本
Class 文件的頭 4 個字節(jié)被稱為魔數(shù)(Magic Number),它的唯一作用是確定該 Class 文件是否能被虛擬機接受,其值為 0xCAFEBABE(咖啡寶貝)。
緊接著魔數(shù)的 4 個字節(jié)存儲的是 Class 文件的版本號:第 5 和第 6 個字節(jié)是次版本號(Minor Version),第 7 和第 8 個字節(jié)是主版本號(Major Version)。Java 版本號從 45 開始,以后每個 JDK 大版本發(fā)布則主版本號加 1。高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能運行以后版本的 Class 文件。
三,常量池
緊接著主、次版本號的是常量池入口,常量池入口需要放置一項 u2 類型的數(shù)據(jù),代表常量池容量計數(shù)值(constant_pool_count),這個容量計數(shù)是從 1 而不是從 0 開始,第 0 項用于表達“不引用任何一個常量池項目”的含義。Class 文件結(jié)構(gòu)中只有常量池的容量計數(shù)是從 1 開始,其他都是從 0 開始。
常量池中主要存放兩大類常量:字面量和符號引用。字面量比較接近 Java 語言層面的常量概念,如文本字符串、被聲明為 final 的常量值等。而符號引用則屬于編譯原理方面的概念,主要包括下面幾類常量:
被模塊導(dǎo)出或開放的包(Package)
類和接口的全限定名(Fully Qualified Name)
字段的名稱和描述符(Descriptor)
方法的名稱和描述符
方法句柄和方法類型(Method Handle、Method Type、Invoke Dynamic)
動態(tài)調(diào)用點和動態(tài)常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
Java 會在虛擬機加載 Class 文件的時候進行動態(tài)連接,將符號引用轉(zhuǎn)換為真正的內(nèi)存入口。常量池中每一項常量都是一個表,最初有 11 種結(jié)構(gòu)不同的表結(jié)構(gòu)數(shù)據(jù),后來為了更好地支持動態(tài)語言調(diào)用,額外增加了 4 種動態(tài)語言相關(guān)的常量,后來為了支持 Java 模塊化,又加入了 2 個常量,所以截止 JDK13,常量表中有 17 種不同類型的常量。這 17 類表都有一個共同的特點,表結(jié)構(gòu)起始的第一位是個 u1 類型的標(biāo)志位(tag)
17 種常量類型所代表的具體含義如表:
| 類型 | 標(biāo)志 | 描述 |
| CONSTANT_Utf8_info | 1 | UTF-8 編碼的字符串 |
| CONSTANT_Integer_info | 3 | 整型字面量 |
| CONSTANT_Float_info | 4 | 浮點型字面量 |
| CONSTANT_Long_info | 5 | 長整型型字面量 |
| CONSTANT_Double_info | 6 | 雙精度浮點型字面量 |
| CONSTANT_Class_info | 7 | 類或接口的符號引用 |
| CONSTANT_String_info | 8 | 字符串類型字面量 |
| CONSTANT_Fieldref_info | 9 | 字段的符號引用 |
| CONSTANT_Methodref_info | 10 | 類中方法的符號引用 |
| CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符號引用 |
| CONSTANT_NameAndType_info | 12 | 字段或方法的部分符號引用 |
| CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
| CONSTANT_MethodType_info | 16 | 表示方法類型 |
| CONSTANT_Dynamic_info | 17 | 表示一個動態(tài)計算常量 |
| CONSTANT_InvokeDynamic_info | 18 | 表示一個動態(tài)方法調(diào)用點 |
| CONSTANT_Moudle_info | 19 | 表示一個模塊 |
| CONSTANT_Package_info | 20 | 表示一個模塊中開放或者導(dǎo)出的包 |
四,訪問標(biāo)志
常量池結(jié)束之后,緊接著的 2 個字節(jié)代表訪問標(biāo)志(access_flags),這個標(biāo)志用于標(biāo)識一些類或者接口層次的訪問信息,包括這個 Class 是類還是接口;是否定義為 public 類型;是否定義為 abstract 類型;如果是類的話,是否被聲明為 final 等等。具體的標(biāo)志位以及標(biāo)志的含義如表:
| 標(biāo)志名稱 | 標(biāo)志值 | 含義 |
| ACC_PUBLIC | 0x0001 | 是否為 Public 類型 |
| ACC_FINAL | 0x0010 | 是否被聲明為 final,只有類可以設(shè)置 |
| ACC_SUPER | 0x0020 | 是否允許使用 invokespecial 字節(jié)碼指令的新語義 |
| ACC_INTERFACE | 0x0200 | 標(biāo)志這是一個接口 |
| ACC_ABSTRACT | 0x0400 | 是否為 abstract 類型,對于接口或者抽象類來說,次標(biāo)志值為真,其他類型為假 |
| ACC_SYNTHETIC | 0x1000 | 標(biāo)志這個類并非由用戶代碼產(chǎn)生 |
| ACC_ANNOTATION | 0x2000 | 標(biāo)志這是一個注解 |
| ACC_ENUM | 0x4000 | 標(biāo)志這是一個枚舉 |
access_flags 中一共有 16 個標(biāo)志位可以使用,當(dāng)前只定義了其中 9 個,沒有使用到的標(biāo)志位要求一律為零。
五,類索引、父類索引與接口索引集合
類索引(this_class)、父類索引(super_class)和接口索引(interfaces)都按順序排列在訪問標(biāo)志之后,類索引和父類索引用兩個 u2 類型的索引值表示,而接口索引是一組 u2 類型的數(shù)據(jù)的集合。
類索引用于確定該類的全限定名,父類索引確定該類的父類的全限定名,由于 Java 不允許多繼承,因此父類索引只有一個,Object 類的父類索引為 0。類索引和父類索引各自指向一個 CONSTANT_Class_info 的類描述符常量,通過這個索引值可以找到定義在 CONSTANT_Utf8_info 類型的常量中的全限定名字符串。
對于接口索引集合,入口的第一項 u2 類型的數(shù)據(jù)為接口計數(shù)器(interfaces_count),表示索引表的容量,如果該類沒有實現(xiàn)任何接口,則該計數(shù)器的值為 0,后面接口的索引表不再占用任何字節(jié)。
六,字段表集合方法表集合
字段表集合(field_info)用于描述接口或類中聲明的變量,包括類級變量以及實例級變量,但不包括在方法內(nèi)部聲明的局部變量。字段包含待信息有字段的作用域(public、private、protected)、是實例變量還是類變量(static)、可變性(final)等等。這些信息要么有,要么沒有,很適合用標(biāo)志位來表示,而字段叫什么,被定義為什么數(shù)據(jù)類型,這些都無法固定,只能用常量池中的常量來描述。
字段表結(jié)構(gòu)如下:
| u2 | access_flags | 1 |
| u2 | name_index | 1 |
| u2 | descriptor_index | 1 |
| u2 | attributes_count | 1 |
| attribute_info | attributes | attributes_count |
access_flags 用來標(biāo)識字段修飾符(public、static、final、volatile ...),name_index 和 descriptor_index 都是對常量池的引用,分別代表字段的簡單名稱以及字段和方法的描述符。之后的是屬性表集合,用于存儲一些額外信息。
字段表集合不會列出從父類或父接口繼承而來的字段,但有可能出現(xiàn)原本 Java 代碼中沒有的字段,例如內(nèi)部類為了保持對外部類的訪問性,編譯器會自動添加指向外部類實例的字段。
方法表集合與字段表集合幾乎完全一致,僅在標(biāo)志和屬性表集合的可選項中有所區(qū)別。至于方法里面的代碼,則經(jīng)編譯后存放在方法屬性表集合中一個名為 Code 的屬性里面。
粉絲福利:Java從入門到入土學(xué)習(xí)路線圖
??????

??長按上方微信二維碼?2 秒
感謝點贊支持下哈?
