虛擬機類加載機制(二)--- 類文件結(jié)構(gòu)之屬性表詳解

Java的技術(shù)體系包括

  • 支持Java程序運行的虛擬機(JVM)
  • 提供接口支持的Java API
  • Java 編程語言
  • 第三方Java框架(如Spring等)

代碼編譯的結(jié)果從本地機器碼轉(zhuǎn)變?yōu)樽止?jié)碼,是存儲格式發(fā)展的一小步,卻是編程語言的一大步。


上一篇文章我們詳細介紹了由java類編譯而成的Class文件的結(jié)構(gòu),其中最后一項屬性表集合(attribute_info)內(nèi)容較多,且我們使用Java編寫的代碼邏輯,大部分都在此表示,因此單獨拿出一篇文章詳細介紹屬性表的內(nèi)容。在第七版的虛擬機規(guī)范中已經(jīng)預(yù)定了21項屬性表,且還可以自定義屬性表,因此屬性表很多,我們詳細了解一下其中最為常用的幾項

Code屬性

一個類中的方法中的代碼,經(jīng)過編譯之后,最終變?yōu)樽止?jié)碼指令存儲在Code屬性內(nèi)。Code屬性位于方法表(method_info)的屬性集合之中,但并非所有的方法都有這個屬性,比如接口或者抽象類的方法就沒有。Code屬相的結(jié)構(gòu)如下圖所示

類型 名稱 數(shù)量 描述
u2 attribute_name_index 1 屬性名稱
u4 attribute_length 1 屬性總長度
u2 max_stack 1 操作數(shù)棧最大深度
u2 max_locals 1 局部變量表所需要的存儲空間,編譯器會根據(jù)局部變量的作用域等計算出該值的大小
u4 code_length 1 字節(jié)碼長度
u1 code code_length 用來存儲字節(jié)碼指令的一系列字節(jié)流
u2 exception_table_length 1 異常表長度
exception_info exception_table exception_table_length 顯示異常處理表
u2 attribute_count 1
attribute_info attributes attributes_count

其中code和code_length用來表示Java程序編譯后生成的字節(jié)碼指令。code_length代表字節(jié)碼的長度,code用于存儲字節(jié)碼指令的一系列字節(jié)流。字節(jié)碼指令,顧名思義,就是一個字節(jié)代表的指令,一個字節(jié)代表的值范圍是0~255,也就是說總共可以表達256種指令,目前虛擬機規(guī)范定義了大約200條指令。code_length是一個u4類型的數(shù)據(jù),理論上表達的意思是字節(jié)碼指令的最大長度可以達到2的32次方-1,但虛擬機規(guī)范中明確規(guī)定一個方法不允許超過65535條字節(jié)碼指令,也就是兩個字節(jié)所能表示的最大值。

max_locals中的單位是Slot,Slot是虛擬機為局部變量分配內(nèi)存所使用的最小單位,對于長度不超過32位的數(shù)據(jù)類型,每個局部變量表占用1個Slot,如byte, char, float, int, short, boolean, returnAdress等。對于double和long這兩種64位數(shù)據(jù)類型需要兩個Slot

Code屬性是Class文件中最最重要的一個屬性。因為一個Java類中表達的信息,我們可以將其分為兩部分元數(shù)據(jù)(包括類、字段、方法定義和其他信息)和代碼邏輯(方法中的代碼),那么在整個Class文件中,Code 屬性用來描述代碼邏輯,其他的數(shù)據(jù)項均是來描述元數(shù)據(jù)的。換句話講,我們所寫代碼的主體邏輯,告訴計算機該做什么事情,都是在Code屬性中包含。

異常表的格式如下所示,具體含義為,如果當字節(jié)碼在第start_pc行到end_pc行之間出現(xiàn)了catch_type類型或者其子類的異常,則轉(zhuǎn)到第handler_pc行繼續(xù)執(zhí)行。

類型 名稱 數(shù)量 類型 名稱 數(shù)量
u2 start_pc 1 u2 handler_pc 1
u2 end_pc 1 u2 catch_type 1

Exceptions 屬性

這里的Exception屬性是在方法表中與Code屬性平級的一項屬性,表示方法描述時,在throws關(guān)鍵字后面列舉的異常。結(jié)構(gòu)如下

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_exceptions 1
u2 exception_index_table number_of_exceptions

LineNumberTable屬性

LineNumberTable屬性用于描述java代碼行號與字節(jié)碼行號(字節(jié)碼的偏移量)之間的對應(yīng)關(guān)系。它不是運行時必須的屬性。如果沒有這項屬性,不會對程序的運行產(chǎn)生任何影響,但是當程序拋出異常時,堆棧中沒有出錯的行號,而且在調(diào)試程序時,也無法按照源碼行來調(diào)試斷點。默認會在Class文件中生成這一項,可以使用如下命令取消生成

javac -g:none或者-g:lines

它的結(jié)構(gòu)如下

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 line_number_table_length 1
line_number_info line_number_table line_number_table_length

其中l(wèi)ine_number_info表包含了start_pc和line_number兩個u2類型的數(shù)據(jù),前者是字節(jié)碼行號,后者是源碼行號。

LocalVariableTable屬性

LocalVariableTable屬性用于描述棧幀中局部變量表中的變量和java源碼中的變量之間的關(guān)系。它也不是運行時必須的屬性,但默認會生成到Class文件中。如果沒有這項屬性,當別人調(diào)用這個方法的時候,所有參數(shù)名稱都會丟失,被arg0,arg1之類的占位符代替,會對代碼編寫帶來不方便。可以使用如下命令取消生成

javac -g:none或者-g:vars

它的結(jié)構(gòu)如下

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 local_variable_table_length 1
local_variable_info lacal_variable_table local_variable_table_length

其中l(wèi)ocal_variable_info表示一個棧幀與源碼中的局部變量的對應(yīng)關(guān)系,結(jié)構(gòu)和含義如下所示

類型 名稱 數(shù)量 描述
u2 start_pc 1 局部變量生命周期開始的字節(jié)碼偏移量
u2 length 1 作用范圍的長度
u2 name_index 1 局部變量名稱,指向常量池的索引
u2 descriptor_index 1 局部變量描述符,指向常量池的索引
u2 index 1 局部變量在棧幀中局部變量表中Slot的位置

SourceFile屬性

SourceFile屬性用于記錄生成這個Class文件的源碼文件名稱。這個屬性也是可選的,如果不生成這項屬性,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名??墒褂萌缦旅钊∠蛇@項屬性

javac -g:none或者-g:source

這個屬性是一個定長的屬性,它的結(jié)構(gòu)如下

類型 名稱 數(shù)量 描述
u2 attribute_name_index 1
u4 attribute_length 1
u2 source_index 指向常量池中CONSTANT_Utf8_info類型常量的索引,常量值是源碼文件的文件名

ConstantValue屬性

ConstantValue屬性的作用是通知虛擬機自動為靜態(tài)變量賦值。只有被static關(guān)鍵字修飾的變量才可以使用這項屬性。對于靜態(tài)變量,有兩種時刻可以賦值,一種是在類構(gòu)造器<clinit>,另一種是使用ConstantValue屬性。各種編譯器的實現(xiàn)可能不同。我們常用的Sun javac編譯器的選擇是,對于同時使用final和static的常量,并且它的類型是基本類型或者String類型,就生成ConstantValue來賦值。如果沒有被final修飾,或者非基本類型或者字符串,則會選擇在<clinit>中進行初始化。從虛擬機規(guī)范的角度來講,ConstantValue屬性并沒有要求必須設(shè)置了ACC_FINAL標志,而是要求了必須設(shè)置有ACC_STATIC標志。

該屬性的結(jié)構(gòu)如下,它也是一個定長的屬性

類型 名稱 數(shù)量 描述
u2 attribute_name_index 1
u4 attribute_length 1
u2 constantcalue_index 指向常量池中的索引

InnerClass屬性

InnerClass屬性用于記錄內(nèi)部類與宿主之間的關(guān)聯(lián)。如果一個類中定義了內(nèi)部類,編譯器就會分別在這個類和它的內(nèi)部類中生成該屬性。該屬性的結(jié)構(gòu)如下

類型 名稱 數(shù)量 描述
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_classes 1 記錄有多少個內(nèi)部類
inner_classes_info inner_classes 1 每個內(nèi)部類是一個該類型的表

inner_classes_info的結(jié)構(gòu)如下

類型 名稱 數(shù)量 描述
u2 inner_class_info_index 1 指向常量池中CONSTANT_Class_info型常量,代表內(nèi)部類的符號引用
u2 outer_class_info_index 1 指向常量池中CONSTANT_Class_info型常量,代表外部類的符號引用
u2 inner_name_index 1 指向常量池中CONSTANT_Utf8_info型常量,代表內(nèi)部類的名稱
u2 inner_class_access_flags 1 代表內(nèi)部類的訪問標志,類似于access_flags

其中inner_class_access_flags標志位的含義如下表

標志名稱 標志值 含義
ACC_PUBLIC 0x0001 內(nèi)部類是否為public
ACC_PRIVATE 0x0002 內(nèi)部類是否為private
ACC_PROTECTED 0x0004 內(nèi)部類是否為protected
ACC_STATIC 0x0008 內(nèi)部類是否為static
ACC_FINAL 0x0010 內(nèi)部類是否為final
ACC_SYNCHRONIZED 0x0020 內(nèi)部類是否為synchronized
ACC_ABSTRACT 0x0400 內(nèi)部類是否為abstract
ACC_SYNTHETIC 0x1000 內(nèi)部類是否由編譯器自動產(chǎn)生
ACC_ANNOTATION 0x2000 內(nèi)部類是一個注解
ACC_ENUM 0x4000 內(nèi)部類是一個枚舉

Deprecated 與 Synthetic 屬性

這兩個屬性都屬于標志類型的布爾屬性,要么有要么沒有,不存在屬性值的概念。其中,Deprecated用于表示某個類或者方法字段已經(jīng)被認定為不推薦使用,它可以通過在代碼中使用 @deprecated 注釋進行設(shè)置。Synthetic屬性代表某個字段或者方法不是由java源代碼產(chǎn)生,而是由編譯器自動添加的。這兩個屬性的結(jié)構(gòu)如下,其中attribute_length的值必須為0。

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1

編譯器自動生成的實例構(gòu)造器<init>和類構(gòu)造器<clinit>除外

StackMapTable屬性

該屬性是JDK1.6之后增加到Class文件規(guī)范中的,它是一個復(fù)雜的變長屬性,位于Code屬性表中。它的功能用于,在類加載的字節(jié)碼驗證階段用于驗證字節(jié)碼邏輯合法性,它的工作原理在虛擬機規(guī)范中用了整整120頁來講解,因此十分復(fù)雜。我們只需要知道該屬性的含義就夠了。它的結(jié)構(gòu)如下

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_entries 1
stack_map_frame stack_map_frame_entries number_of_entries

虛擬機規(guī)范中規(guī)定,在Class版本號大于50的文件中,如果方法的Code屬性中沒有StackMapTable屬性,則表示它帶有一個隱式的StackMap屬性。這個StackMapTable屬性的作用等同于number_of_entries值為0的屬性。一個方法的Code屬性最多只能有一個StackMapTable屬性,否則會拋出ClassFormatError異常。

Signature屬性

Signature屬性在JDK1.5之后增加到了Class文件中,它是一個可選的定長屬性,可以出現(xiàn)在類、字段、方法的屬性表中。它的作用是,任何類、接口、初始化成員或者方法的泛型簽名如果包含了類型變量或參數(shù)化類型,則Signature會為它記錄泛型簽名信息。它的在結(jié)構(gòu)如下

類型 名稱 數(shù)量
u2 attribute_name_index 1
u4 attribute_length 1
u2 signature_index 1

其中signature_index項的值是一個對常量池的索引。常量池在該索引處的項是一個CONSTANT_Utf8_info結(jié)構(gòu),表示類簽名,方法類型簽名或者字段簽名。

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

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

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