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