Class类文件的结构
2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
Class類(lèi)文件的結(jié)構(gòu)
Class類(lèi)文件的結(jié)構(gòu)
任何一個(gè)Class文件都對(duì)應(yīng)著唯一一個(gè)類(lèi)或接口的定義信息,但反之類(lèi)和接口并不一定定義在文件里(比如類(lèi)和接口也可以通過(guò)類(lèi)加載器直接生成)。
Class文件是一組以8位字節(jié)為基礎(chǔ)單位的二進(jìn)制流,各個(gè)數(shù)據(jù)項(xiàng)目嚴(yán)格按照順序緊湊的排列在Class文件中,中間沒(méi)有任何分隔符。Class文件的結(jié)構(gòu)只有兩種數(shù)據(jù)類(lèi)型:無(wú)符號(hào)數(shù)和表。
下面是Class文件的格式:
?
?
魔數(shù)(magic)與Class文件的版本
每個(gè)Class文件的頭四個(gè)字節(jié)稱(chēng)為魔數(shù),它的唯一作用就是確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件。class文件的魔數(shù)值為CA FE BA BE。
緊挨著魔數(shù)后面的四個(gè)字節(jié)存儲(chǔ)的是Class文件的版本號(hào):第五和第六是次版本號(hào)(minor_version),第七和第八是主版本號(hào)(major_version),以下面的類(lèi)為例:
public class TestClass{private String m;public String test() {return m + 1;} }生成的Java Class文件結(jié)構(gòu)為:
?
可以看到代表次版本號(hào)的第五個(gè)和第六個(gè)字節(jié)為0x0000,主版本號(hào)為0x0032,也就是十進(jìn)制的50。
?
常量池
緊接著主版本號(hào)之后是常量池容量計(jì)數(shù)值(constant_pool_count),由于常量池中常量的數(shù)量不是固定的,所以在常量池的入口需要放置一項(xiàng)u2類(lèi)型的數(shù)據(jù),代表常量池容量計(jì)數(shù)值(constant_pool_count),這個(gè)常量技術(shù)值是從1開(kāi)始而不是從0開(kāi)始的。上圖中常量池容量為0x0016,即十進(jìn)制中的22,這就表示常量池中有21項(xiàng)常量。
然后就是常量池(constant_pool),常量池可以理解為Class文件之中的資源倉(cāng)庫(kù),它是Class文件結(jié)構(gòu)中與其他項(xiàng)目關(guān)聯(lián)最多的數(shù)據(jù)類(lèi)型,也是占用Class文件空間最大的數(shù)據(jù)項(xiàng)目之一,同時(shí)還是在Class文件中第一個(gè)出現(xiàn)的表類(lèi)型數(shù)據(jù)項(xiàng)目。
常量池中主要存放兩大類(lèi)常量:字面量和符號(hào)引用。
- 字面量近似于Java的常量,如文本字符串、聲明為final的常量值。
- 而符號(hào)引用則屬于編譯原理方面的概念,包括:
- 類(lèi)和接口的全限定名
- 字段的名稱(chēng)和描述符
- 方法的名稱(chēng)和描述符
Java代碼在進(jìn)行javac編譯時(shí)沒(méi)有連接的步驟,而是在虛擬機(jī)加載Class文件的時(shí)候進(jìn)行動(dòng)態(tài)連接。當(dāng)虛擬機(jī)運(yùn)行時(shí),需要從常量池獲得對(duì)應(yīng)的符號(hào)引用,再在類(lèi)創(chuàng)建時(shí)或運(yùn)行時(shí)解析、翻譯到具體的內(nèi)存地址之中。
個(gè)人理解符號(hào)引用的作用就是在編譯時(shí)記錄下文件的類(lèi)、字段和方法,在JVM運(yùn)行時(shí)能在需要的時(shí)候獲取相應(yīng)信息進(jìn)行加載。
在常量池中會(huì)有一部分自動(dòng)生成的常量(這些常量沒(méi)有在Java代碼里面直接出現(xiàn)過(guò)),但這些常量會(huì)被字段表、方法表、屬性表引用,用來(lái)描述一些不方便使用“固定字節(jié)”表達(dá)的內(nèi)容。(比如:方法的返回值?有幾個(gè)參數(shù)?參數(shù)類(lèi)型?等)
訪問(wèn)標(biāo)志
在常量池結(jié)束之后,有兩個(gè)字節(jié)代表訪問(wèn)標(biāo)志(access_flags),這個(gè)標(biāo)志用于識(shí)別一些類(lèi)和接口層次的訪問(wèn)信息,包括這個(gè)Class是類(lèi)還是接口,是否為public類(lèi)型,是否定義為abstract類(lèi)型,如果是類(lèi)的話,是否聲明為final類(lèi)型等。
類(lèi)索引、父類(lèi)索引和接口索引集合
類(lèi)索引(this_class)和父類(lèi)索引(super_class)都是一個(gè)u2類(lèi)型的數(shù)據(jù),而接口索引集合(包括了interfaces_count及interfaces)是一組u2類(lèi)型的數(shù)據(jù)的集合,Class文件中由這個(gè)三個(gè)數(shù)據(jù)來(lái)確定類(lèi)的繼承關(guān)系。它們按順序排在訪問(wèn)標(biāo)志后面。
類(lèi)索引用于確定這個(gè)類(lèi)的全限定名,
父類(lèi)索引用于確定這個(gè)類(lèi)的父類(lèi)的全限定名。由于Java語(yǔ)言不允許多重繼續(xù),所以父類(lèi)索引只有一個(gè)(除了java.lang.Object外所有的Java類(lèi)都有父類(lèi),除了它所有的父類(lèi)索引都不為零)。
接口索引集合就用來(lái)描述這個(gè)類(lèi)實(shí)現(xiàn)了哪些接口,這些被實(shí)現(xiàn)的接口將按照Implements語(yǔ)句后的接口順序從左到右排列在索引集合中。
字段表集合
字段表(field_info)用于描述接口或類(lèi)中聲明的變量。字段包括類(lèi)級(jí)變量以及實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的局部變量。可以包括的信息有:字段的作用域、是實(shí)例變量還是類(lèi)變量(static 修飾符)、可變性(final)、并發(fā)可見(jiàn)性(volatile修飾)、是否可被序列化(transient修飾符)、字段數(shù)據(jù)類(lèi)型、字段名稱(chēng)。上述的這些信息,各個(gè)修飾符都是布爾值。
字段叫什么名字、字段被定義為什么類(lèi)型,這些是無(wú)法固定的,只能引用常量池中的常量來(lái)描述。
?
描述符描述方法:
?
字段表集合中不會(huì)列出從超類(lèi)或者父接口中繼承而來(lái)的字段,但可能列出Java代碼中不存在的字段,如內(nèi)部類(lèi)中保持對(duì)外部類(lèi)的訪問(wèn)性,會(huì)自動(dòng)添加指向外部類(lèi)實(shí)例的字段;
方法表集合
方法里的Java代碼經(jīng)過(guò)編譯器編譯成字節(jié)碼指令后,存放在屬性表集合中一個(gè)名叫“Code”的屬性里面。
如果父類(lèi)方法在子類(lèi)中沒(méi)有被重寫(xiě),那么方法表集合中不會(huì)出現(xiàn)來(lái)自父類(lèi)的方法;同時(shí)也同樣可能出現(xiàn)編譯器自動(dòng)添加的方法,典型如“類(lèi)構(gòu)造器< clinit >”和實(shí)例構(gòu)造器"< init >"
如果要重載一個(gè)方法除了要與原方法具有相同的簡(jiǎn)單名稱(chēng)之外,還必須要求擁有一個(gè)與原方法不同的特征簽名,即方法中各個(gè)參數(shù)在常量池中的字段符號(hào)引用集合,不包含返回值。這就是Java語(yǔ)言里僅僅依靠返回值不同無(wú)法對(duì)一個(gè)已有方法重載。但是在Class文件格式中即字節(jié)碼層面(前面是Java代碼層面),方法特征還包括方法返回值及受查異常表,因此2個(gè)不是完全一致的方法也可以合法共存與一個(gè)Class文件中。
屬性表集合
- 在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,用于描述某些場(chǎng)景專(zhuān)有的信息。
- 屬性表要求稍微寬松,不再要求各個(gè)屬性表具有嚴(yán)格執(zhí)行順序,只要不與現(xiàn)有屬性名不重復(fù)即可。
以下是虛擬機(jī)規(guī)范定義的屬性:
?
?
Code屬性
Code屬性:Java程序方法體中的代碼經(jīng)過(guò)Javac編譯器處理后,最終變?yōu)樽止?jié)碼指令存儲(chǔ)在Code屬性內(nèi)。Code屬性出現(xiàn)在方法表的屬性集合中,但是并非所有的方法表都必須存在這個(gè)屬性,例如接口或者抽象類(lèi)中的方法就不存在Code屬性。其屬性表結(jié)構(gòu)如下:
?
?
- attribute_name_index:指向CONSTANT_Utf8_info型常量的索引,固定值為“Code”代表該屬性的屬性名稱(chēng)。
- attribute_length:屬性值的長(zhǎng)度(即屬性表的長(zhǎng)減去6個(gè)字節(jié),這六個(gè)字節(jié)為attribute_name_index及attribute_length)
- max_stack:操作數(shù)棧深度的最大值(JVM運(yùn)行時(shí)根據(jù)這個(gè)值來(lái)分配操作棧深度)
- max_locals:局部變量表所需的儲(chǔ)存空間(單位Slot)。對(duì)于byte,char,float,int,shot,boolean,reference和returnAddress等長(zhǎng)度不超過(guò)32位的數(shù)據(jù)類(lèi)型,每個(gè)局部變量占1個(gè)Slot,而double與long這兩種64位的數(shù)據(jù)類(lèi)型而需要2個(gè)Slot來(lái)存放。編譯器會(huì)根據(jù)變量的作用域來(lái)分類(lèi)Slot并分配給各個(gè)變量使用,然后計(jì)算出max_locals的大小。
- code_length、code:用來(lái)存儲(chǔ)Java源程序編譯后生成的字節(jié)碼指令。
Exceptions屬性
Exceptions屬性:其作用是列舉出可能拋出的受查異常,也就是方法描述時(shí)在throws關(guān)鍵字后面列舉的異常。
?
?
- number_of_exceptions:表示可能拋出number_of_exceptions種受檢查異常,每一種受檢查異常使用一個(gè)exception_index_table項(xiàng)表示。
- exception_index_table:指向常量池中CONSTANT_Class_info型常量表的索引,代表了該受檢查異常的類(lèi)型。
LineNumberTable屬性
LineNumberTable屬性用于描述Java源代碼行號(hào)與字節(jié)碼行號(hào)(字節(jié)碼偏移量)之間的對(duì)應(yīng)關(guān)系。如果選擇不生成LineNumberTable屬性表,在拋出異常時(shí)堆棧中將不會(huì)顯示出錯(cuò)的行號(hào),也無(wú)法在調(diào)試程序的時(shí)候按照源碼來(lái)設(shè)置斷點(diǎn)。
?
?
?
LocalVariableTable屬性
LocalVariableTable屬性表用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關(guān)系。非必須。如果沒(méi)有生成這項(xiàng)屬性,IDE可能會(huì)使用諸如arg0、arg1之類(lèi)的占位符來(lái)替換原有的參數(shù)名稱(chēng),對(duì)程序運(yùn)行沒(méi)有影響。
SourceFile屬性
SourceFile屬性用于記錄這生成這個(gè)Class文件的源碼文件名稱(chēng)。這個(gè)屬性也是可選的,如果不生成這項(xiàng)屬性,當(dāng)拋出異常時(shí),堆棧中將不會(huì)顯示出錯(cuò)誤代碼所屬的文件名。
?
?
?ConstantValue屬性
作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值,只有被static關(guān)鍵字修飾的變量才可以用這個(gè)屬性。
- 對(duì)于非static類(lèi)型的變量(實(shí)例變量)的賦值是在實(shí)例構(gòu)造器< init >方法中進(jìn)行的。
- 而對(duì)于類(lèi)變量(static變量)有兩種方式:在類(lèi)構(gòu)造器< clinit >方法中或者使用ConstantValue屬性。
目前Sun javac編譯器的選擇是:同時(shí)使用final和static修飾的變量且為基本數(shù)據(jù)類(lèi)型或String類(lèi)型使用ConstantValue屬性初始化,否則使用類(lèi)構(gòu)造器< clinit >進(jìn)行初始化。
?
?
InnerClasses屬性
用于記錄內(nèi)部類(lèi)與宿主類(lèi)之間的關(guān)聯(lián)。
Inneclasses屬性結(jié)構(gòu):
?
inner_classes_info表的結(jié)構(gòu):
?
?
?
?Deprecated及Synthetic屬性
Deprecated及Synthetic屬性都屬性于標(biāo)志類(lèi)型的布爾值屬性,只存在有和沒(méi)有的區(qū)別,沒(méi)有屬性值的概念。所以在下圖屬性結(jié)構(gòu)中attribute_length的數(shù)據(jù)值必須為0x00000000。
- Deprecated屬性用于表示某個(gè)類(lèi),字段或方法,已經(jīng)被程序作者定為不再推薦使用,它可以通過(guò)代碼中使用@Deprecated注解進(jìn)行設(shè)置。
- Synthetic屬代表此字段或方法并不是由Java源碼直接產(chǎn)生的,而是由編譯器自行添加的。(jdk1.5之后還可以通過(guò)設(shè)置訪問(wèn)標(biāo)志中的ACC_SYNTHETIC標(biāo)志位表示。)
?
?
StackMapTable屬性
這是一個(gè)復(fù)雜的變長(zhǎng)屬性,位于Code屬性的屬性表中。這個(gè)屬性會(huì)在虛擬機(jī)類(lèi)加載的字節(jié)碼驗(yàn)證階段被新類(lèi)型檢查驗(yàn)證器使用,目的在于代替以前比較消耗性能的基于數(shù)據(jù)流分析的類(lèi)型推導(dǎo)驗(yàn)證器。
?
?
Signature屬性
一個(gè)可選的定長(zhǎng)屬性,在JDK 1.5發(fā)布后增加的,任何類(lèi)、接口、初始化方法或成員的泛型簽名如果包含了類(lèi)型變量或參數(shù)化類(lèi)型,則Signature屬性會(huì)為它記錄泛型簽名信息。這主要是因?yàn)镴ava的泛型采用的是擦除法實(shí)現(xiàn)的偽泛型,在字節(jié)碼中泛型信息編譯之后統(tǒng)統(tǒng)被擦除,在運(yùn)行期無(wú)法將泛型類(lèi)型與用戶定義的普通類(lèi)型同等對(duì)待。通過(guò)Signature屬性,Java的反射API能夠獲取泛型類(lèi)型。
?
?
BootstrapMethods屬性
一個(gè)復(fù)雜的變長(zhǎng)屬性,位于類(lèi)文件的屬性表中,用于保存invokedynamic指令引用的引導(dǎo)方法限定符。(最多只能有一個(gè))
轉(zhuǎn)載于:https://my.oschina.net/PrivateO2/blog/1575781
與50位技術(shù)專(zhuān)家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Class类文件的结构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ORACLE wallet实现无需输入用
- 下一篇: 深入理解JVM类文件格式