深入理解Java虛擬機筆記---屬性表集合
在Class文件,字段表,方法表中都可以攜帶自己的屬性表集合,以用於描述某些場景專有的信息。與Class文件中其它的數據項目要求的順序、長度和內容不同,屬性表集合的限制稍微寬松一些,不再要求各個屬性表具有嚴格的順序,並且只要不與已有的屬性名重復,任何人實現的編譯器都可以向屬性表中寫入自己定義的屬性信息,Java虛擬機運行時會忽略掉它不認識的屬性。為了能正確地解析Class文件,《Java虛擬機規范(第二版)》中預定義了9薦虛擬機實現應當能識別的屬性,具體如下表所示:
對於每個屬性,它的名稱需要從常量池中引用一個CONSTANT_Utf8_info類型的常量表來表示,而屬性值的結構則是完全自定義的,只要說明屬性值所占用的位數長度即可。一個符合規則的屬性表應該滿足如下表定義的結構:
1.Code屬性
Java程序方法體里的代碼經過Javac編譯器處理之后,最終變為字節碼指令存儲在Code屬性內。Code屬性出現在方法表的屬性集合中,但並非所有方法都必須存在這個屬性表,譬如接口或抽象類中的抽象方法就不存在Code屬性,如果方法有Code屬性表存在,那么它的結構如下表:
attribute_name_index是一項指向CONSTANT_Utf8_info常量表的索引,常量值固定為“Code”,它代表了該屬性的屬性名稱,attribute_length指示了屬性值的長度,由於屬性名稱索引與屬性長度一共是6個字節,所以屬性值的長度固定為整個屬性表的長度減去6個字節。
max_stack代表了操作數棧(Operand Stacks)的最大深度。在方法執行的任意時刻,操作數棧都不會超過這個深度。虛擬機運行的時候需要根據這個值來分配棧幀(Frame)中的操作數棧深度。
max_locals代表了局部變量表所需的存儲空間。在這里,max_locals的單位是Slot,Slot是虛擬機為局部變量表分配內存所使用的最小單位。對於byte,char,float,int,shot,boolean,reference和returnAddress等長度不超過32位的數據類型,每個局部變量占1個Slot,而double與long這兩種64位的數據類型而需要2個Slot來存放。方法參數(包括實例方法中的隱藏參數“this”),顯示異常處理器的參數(Exception Handler Parameter,即try-catch語句中catch塊所定義的異常),方法體中定義的局部變量都需要使用局部表來存放。另外,並不是在方法中使用了多個局部變量,就把這些局部變量所占的Slot之和作為max_locals的值,原因是局部變量表中的Slot可以重用,當代碼執行超出一個局部變量的作用域時,這個局部變量所在的Slot就可以被其他局部變量所使用,編譯器會根據變量的作用域來分類Slot並分配給各個變量使用,然后計算出max_locals的大小。
code_length和code用來存儲Java源程序編譯后生成的字節碼指令。code_length代表字節碼長度,code是用於存儲字節碼指令的一系列字節流。既然名為字節碼指令,那么每個指令就是一個u1類型的單字節,當虛擬機讀取到code中的一個字節碼時,就可相應地找出這個字節碼代表的是什么指令,並且可以知道這條指令后面是否需要跟隨參數,以及參數應該如何理解。
關於code_length還有一件值得注意的事情,雖然它是一個u4類型的長度值,理論上最大值可以達到2的32次方減1,但虛擬機規范中限制了一個方法不允許超過65535條字節碼指令,如果超過這個限制,Javac編譯器就會拒絕編譯。一般來講,只要我們寫Java代碼時不是刻意地編寫超長的方法,就不會超過這個最大值限制。但是,在編譯復雜的JSP文件中,可以會因為這個原因導致編譯失敗。
Code屬性是Class文件中最重要的一個屬性,如果表一個Java程序中的信息分為代碼(Code,方法體里的Java代碼)和元數據(Metadata,包括類、字段、方法定義及其它信息)兩部分,那么在整個Class文件里,Code屬性用於描述代碼,其它的所有數據項目就都用於描述元數據。
在字節碼指令之后的是這個方法的顯示異常處理表,異常表對於Code屬性表來說不是必須存在的。異常表的格式如下表:
異常表它包含4個字段,這些字段的含義為:如果字節碼從第start_pc到end_pc行之間(不包含第end_pc)行出現了類型為catch_type或其子類的異常(catch_type為指向一個CONSTANT_Class_info型常量的索引),則轉到第handler_pc行繼續處理。當catch_type的值為0時,代表任何的異常情況都需要轉向到handler_pc行行進行處理。異常表實際上是Java代碼的一部分,編譯器使用異常表而不是簡單的跳轉命令來實現Java異常及finally處理機制。注:字節碼的“行”是一種形象的描述,指的是字節碼相對於方法體開始的偏移量,而不是Java源代碼的行號。
2.Exceptions屬性
這里的Exceptions屬性是在方法表中與Code屬性平級的一項屬性,而不是Code屬性表中的異常屬性表。Exceptions屬性表的作是列舉出方法中可能拋出的受查檢(Checked Exception),也就是在方法描述時在throws關鍵字后面列舉的異常。它的結構如下表:
此屬性表中的number_of_exceptions項表示訪求可能拋出number_of_exceptions種受檢查異常,每一種受檢查異常使用一個exception_index_table項表示,為指向常量池中CONSTANT_Class_info型常量表的索引,代表了該受檢查異常的類型。
3.LineNumberTable屬性
LineNumberTable屬性用於描述Java源代碼行號與字節碼行號(字節碼偏移量)之間的對應關系。它並不是運行時必須的屬性,但默認會生成到Class文件之中,可以在Javac中使用-g:none或-g:lines選項來取消或要求生成這項信息。如果選擇不生成LineNumberTable屬性表,對程序運行產生的最主要的影響就是在拋出異常時,堆棧中將不會顯示出錯的行號,並且在調試程序的時候無法按照源碼來設置斷點。LineNumberTable屬性表結構如下表:
line_number_table是一個數量為line_number_table_length,類型為line_number_info的集合,line_number_info表包括了start_pc和line_number兩個u2類型的數據項,前者是字節碼行號,后者是Java源碼行號。
4.LocalVariableTable屬性
LocalVariableTable屬性表用於描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關系,它不是運行時必須的屬性,默認也不會生成到Class文件之中,可以使用-g:none或-g:vars選項來取消或要求生成這項信息。如果沒有生成這項屬性,最大的影響就是當其它人引用這個方法時,所有參數名稱都丟失,IDE可能會使用諸如arg0、arg1之類的占位符來替換原有的參數名稱,這對程序運行沒有影響,但是會給代碼編寫帶來較大的不便,而且在調試期間無法根據參數名稱從運行上下文件中獲取參數值。LocalVariableTable屬性表結構如下:
其中local_variable_info項目代表了一個棧幀與源碼中的局部變量的關聯,結構如下:
index是這個局部變量在棧幀局部變量表中的Slot位置。當這個變量的數據類型是64位時(double和long),它占用的Slot為index和index+1兩個位置。
在JDK1.5引入了泛型之后,LocalVariableTable屬性增加了一個“姐妹”屬性:LocalVaiableTypeTable,這個新增加的屬性結構與LocalVariableTable屬性非常相似,僅僅是把記錄字段描述符的descript_index替換成了字段的特征簽名(Singnature),對於非泛型類型來說,描述符的參數化類型被擦除掉了,描述符就不能准確地描述泛型類型了,因此出現了LocalVariableTypeTable屬性。
5.SourceFile屬性
SourceFile屬性用於記錄這生成這個Class文件的源碼文件名稱。這個屬性也是可選的,可以使用-g:none或-g:source選項來取消或要求生成這項信息。在Java中,對於大多數的類來說,類名和文件是一致的,但有一些特殊情況(如內部類)例外。如果不生成這項屬性,當招聘異常時,堆棧中半不會顯示出錯誤代碼所屬性文件名。這個屬性是一個室長的屬性,結構如下:
sourcefile_index數據項是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源文件的文件名。
6.ConstantValue屬性
ConstantValue屬性的作用是通知虛擬機自動為靜態變量賦值。只有被static關鍵字修飾的變量才可以使用這項屬性。在Java程序里類類似“int x = 123“和”static int x = 123”這樣的變量定義非常常見,但虛擬機對這兩種變量賦值的方法和時刻有所不同。對於非static類型的變量(也就是實例變量)的賦值是在實例構造器<init>方法中進行的;對於類變量,則有兩種式可以選擇:賦值在類構造器<clinit>方法中進行,或者使用ConstantValue屬性來賦值。目前Sun Javac編譯器的選擇是:如果同時使用final和static來修改一個變量,並且這個變量的數據類型是基本類型或java.lang.String的話,就生成ConstantValue屬性來進行初始化,如果這個變量沒有被final修飾,或者並非基本類型或字符串,則選擇在<client>類構造器中進行初始化。ConstantValue屬性表結構如下:
ConstantValue屬性是一個定長屬性,它的attribute_length數據項值必須為2。constantvalue_index數據項代表了常量池中一個字面常量的引用,根據字段類型不同,字面量可以是CONSTANT_Long_info,CONSTANT_Float_info,CONSTANT_Double_info,CONSTANT_Integer_info和CONSTANT_String_info常量中的一種。
7.InnerClasses屬性
InnerClasses屬性表用於記錄內部類與宿主類之間的關聯。如果一個類中定義了內部類,那么編譯器將會為它及它所包含的內部類生成InnerClasses屬性表。表結構如下:
數據項number_of_classes代表需要記錄多少個內部類信息,每一個內部類的類的信息都由一個inner_class_info表進行描述。inner_class_info表結構如下:
inner_class_info_index和outer_class_info_index都是指向常量池中CONSTANT_Class_infon常量的索引,分別代表了內部類和宿主類的符號引用。inner_name_index是指向常量池中CONSTANT_Utf8_info型常量的索引,代表這個內部類的名稱,如果是匿名內部類,則這項值為0。inner_class_access_flags是內部類的訪問標志,類型於類的access_flags,它的取值范圍如下表:
8.Deprecated及Synthetic屬性
Deprecated及Synthetic屬性都屬性於標志類型的布爾值屬性,只存在有和沒有的區別,沒有屬性值的概念。
Deprecated屬性用於表示某個類,字段或方法,已經被程序作者定為不再推薦使用,它可以通過代碼中使用@Deprecated注解進行設置。
Synthetic屬代表此字段或方法並不是由Java源碼直接產生的,而是由編譯器自行添加的,在JDK1.5之后,標識一個類,字段或方法是編譯器自動產生的,也可以設置它們訪問標志中的ACC_SYNTHETIC標志位,其中最典型的就是Bridge Method。所有非用戶代碼生產的類,方法及字段都應當至少設置Synthetic屬性和ACC_SYNTHETIC標志位中的一項,唯一的例外是實例構造器“<init>”方法和類構造器“<clinit”方法。
Deprecated及Synthetic屬性表結構如下:
其中attribute_length數據項的值必須為0,因為沒有任何屬性值需要設置。

對於每個屬性,它的名稱需要從常量池中引用一個CONSTANT_Utf8_info類型的常量表來表示,而屬性值的結構則是完全自定義的,只要說明屬性值所占用的位數長度即可。一個符合規則的屬性表應該滿足如下表定義的結構:

1.Code屬性
Java程序方法體里的代碼經過Javac編譯器處理之后,最終變為字節碼指令存儲在Code屬性內。Code屬性出現在方法表的屬性集合中,但並非所有方法都必須存在這個屬性表,譬如接口或抽象類中的抽象方法就不存在Code屬性,如果方法有Code屬性表存在,那么它的結構如下表:

attribute_name_index是一項指向CONSTANT_Utf8_info常量表的索引,常量值固定為“Code”,它代表了該屬性的屬性名稱,attribute_length指示了屬性值的長度,由於屬性名稱索引與屬性長度一共是6個字節,所以屬性值的長度固定為整個屬性表的長度減去6個字節。
max_stack代表了操作數棧(Operand Stacks)的最大深度。在方法執行的任意時刻,操作數棧都不會超過這個深度。虛擬機運行的時候需要根據這個值來分配棧幀(Frame)中的操作數棧深度。
max_locals代表了局部變量表所需的存儲空間。在這里,max_locals的單位是Slot,Slot是虛擬機為局部變量表分配內存所使用的最小單位。對於byte,char,float,int,shot,boolean,reference和returnAddress等長度不超過32位的數據類型,每個局部變量占1個Slot,而double與long這兩種64位的數據類型而需要2個Slot來存放。方法參數(包括實例方法中的隱藏參數“this”),顯示異常處理器的參數(Exception Handler Parameter,即try-catch語句中catch塊所定義的異常),方法體中定義的局部變量都需要使用局部表來存放。另外,並不是在方法中使用了多個局部變量,就把這些局部變量所占的Slot之和作為max_locals的值,原因是局部變量表中的Slot可以重用,當代碼執行超出一個局部變量的作用域時,這個局部變量所在的Slot就可以被其他局部變量所使用,編譯器會根據變量的作用域來分類Slot並分配給各個變量使用,然后計算出max_locals的大小。
code_length和code用來存儲Java源程序編譯后生成的字節碼指令。code_length代表字節碼長度,code是用於存儲字節碼指令的一系列字節流。既然名為字節碼指令,那么每個指令就是一個u1類型的單字節,當虛擬機讀取到code中的一個字節碼時,就可相應地找出這個字節碼代表的是什么指令,並且可以知道這條指令后面是否需要跟隨參數,以及參數應該如何理解。
關於code_length還有一件值得注意的事情,雖然它是一個u4類型的長度值,理論上最大值可以達到2的32次方減1,但虛擬機規范中限制了一個方法不允許超過65535條字節碼指令,如果超過這個限制,Javac編譯器就會拒絕編譯。一般來講,只要我們寫Java代碼時不是刻意地編寫超長的方法,就不會超過這個最大值限制。但是,在編譯復雜的JSP文件中,可以會因為這個原因導致編譯失敗。
Code屬性是Class文件中最重要的一個屬性,如果表一個Java程序中的信息分為代碼(Code,方法體里的Java代碼)和元數據(Metadata,包括類、字段、方法定義及其它信息)兩部分,那么在整個Class文件里,Code屬性用於描述代碼,其它的所有數據項目就都用於描述元數據。
在字節碼指令之后的是這個方法的顯示異常處理表,異常表對於Code屬性表來說不是必須存在的。異常表的格式如下表:

異常表它包含4個字段,這些字段的含義為:如果字節碼從第start_pc到end_pc行之間(不包含第end_pc)行出現了類型為catch_type或其子類的異常(catch_type為指向一個CONSTANT_Class_info型常量的索引),則轉到第handler_pc行繼續處理。當catch_type的值為0時,代表任何的異常情況都需要轉向到handler_pc行行進行處理。異常表實際上是Java代碼的一部分,編譯器使用異常表而不是簡單的跳轉命令來實現Java異常及finally處理機制。注:字節碼的“行”是一種形象的描述,指的是字節碼相對於方法體開始的偏移量,而不是Java源代碼的行號。
2.Exceptions屬性
這里的Exceptions屬性是在方法表中與Code屬性平級的一項屬性,而不是Code屬性表中的異常屬性表。Exceptions屬性表的作是列舉出方法中可能拋出的受查檢(Checked Exception),也就是在方法描述時在throws關鍵字后面列舉的異常。它的結構如下表:

此屬性表中的number_of_exceptions項表示訪求可能拋出number_of_exceptions種受檢查異常,每一種受檢查異常使用一個exception_index_table項表示,為指向常量池中CONSTANT_Class_info型常量表的索引,代表了該受檢查異常的類型。
3.LineNumberTable屬性
LineNumberTable屬性用於描述Java源代碼行號與字節碼行號(字節碼偏移量)之間的對應關系。它並不是運行時必須的屬性,但默認會生成到Class文件之中,可以在Javac中使用-g:none或-g:lines選項來取消或要求生成這項信息。如果選擇不生成LineNumberTable屬性表,對程序運行產生的最主要的影響就是在拋出異常時,堆棧中將不會顯示出錯的行號,並且在調試程序的時候無法按照源碼來設置斷點。LineNumberTable屬性表結構如下表:

line_number_table是一個數量為line_number_table_length,類型為line_number_info的集合,line_number_info表包括了start_pc和line_number兩個u2類型的數據項,前者是字節碼行號,后者是Java源碼行號。
4.LocalVariableTable屬性
LocalVariableTable屬性表用於描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關系,它不是運行時必須的屬性,默認也不會生成到Class文件之中,可以使用-g:none或-g:vars選項來取消或要求生成這項信息。如果沒有生成這項屬性,最大的影響就是當其它人引用這個方法時,所有參數名稱都丟失,IDE可能會使用諸如arg0、arg1之類的占位符來替換原有的參數名稱,這對程序運行沒有影響,但是會給代碼編寫帶來較大的不便,而且在調試期間無法根據參數名稱從運行上下文件中獲取參數值。LocalVariableTable屬性表結構如下:

其中local_variable_info項目代表了一個棧幀與源碼中的局部變量的關聯,結構如下:

index是這個局部變量在棧幀局部變量表中的Slot位置。當這個變量的數據類型是64位時(double和long),它占用的Slot為index和index+1兩個位置。
在JDK1.5引入了泛型之后,LocalVariableTable屬性增加了一個“姐妹”屬性:LocalVaiableTypeTable,這個新增加的屬性結構與LocalVariableTable屬性非常相似,僅僅是把記錄字段描述符的descript_index替換成了字段的特征簽名(Singnature),對於非泛型類型來說,描述符的參數化類型被擦除掉了,描述符就不能准確地描述泛型類型了,因此出現了LocalVariableTypeTable屬性。
5.SourceFile屬性
SourceFile屬性用於記錄這生成這個Class文件的源碼文件名稱。這個屬性也是可選的,可以使用-g:none或-g:source選項來取消或要求生成這項信息。在Java中,對於大多數的類來說,類名和文件是一致的,但有一些特殊情況(如內部類)例外。如果不生成這項屬性,當招聘異常時,堆棧中半不會顯示出錯誤代碼所屬性文件名。這個屬性是一個室長的屬性,結構如下:

sourcefile_index數據項是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源文件的文件名。
6.ConstantValue屬性
ConstantValue屬性的作用是通知虛擬機自動為靜態變量賦值。只有被static關鍵字修飾的變量才可以使用這項屬性。在Java程序里類類似“int x = 123“和”static int x = 123”這樣的變量定義非常常見,但虛擬機對這兩種變量賦值的方法和時刻有所不同。對於非static類型的變量(也就是實例變量)的賦值是在實例構造器<init>方法中進行的;對於類變量,則有兩種式可以選擇:賦值在類構造器<clinit>方法中進行,或者使用ConstantValue屬性來賦值。目前Sun Javac編譯器的選擇是:如果同時使用final和static來修改一個變量,並且這個變量的數據類型是基本類型或java.lang.String的話,就生成ConstantValue屬性來進行初始化,如果這個變量沒有被final修飾,或者並非基本類型或字符串,則選擇在<client>類構造器中進行初始化。ConstantValue屬性表結構如下:

ConstantValue屬性是一個定長屬性,它的attribute_length數據項值必須為2。constantvalue_index數據項代表了常量池中一個字面常量的引用,根據字段類型不同,字面量可以是CONSTANT_Long_info,CONSTANT_Float_info,CONSTANT_Double_info,CONSTANT_Integer_info和CONSTANT_String_info常量中的一種。
7.InnerClasses屬性
InnerClasses屬性表用於記錄內部類與宿主類之間的關聯。如果一個類中定義了內部類,那么編譯器將會為它及它所包含的內部類生成InnerClasses屬性表。表結構如下:

數據項number_of_classes代表需要記錄多少個內部類信息,每一個內部類的類的信息都由一個inner_class_info表進行描述。inner_class_info表結構如下:

inner_class_info_index和outer_class_info_index都是指向常量池中CONSTANT_Class_infon常量的索引,分別代表了內部類和宿主類的符號引用。inner_name_index是指向常量池中CONSTANT_Utf8_info型常量的索引,代表這個內部類的名稱,如果是匿名內部類,則這項值為0。inner_class_access_flags是內部類的訪問標志,類型於類的access_flags,它的取值范圍如下表:

8.Deprecated及Synthetic屬性
Deprecated及Synthetic屬性都屬性於標志類型的布爾值屬性,只存在有和沒有的區別,沒有屬性值的概念。
Deprecated屬性用於表示某個類,字段或方法,已經被程序作者定為不再推薦使用,它可以通過代碼中使用@Deprecated注解進行設置。
Synthetic屬代表此字段或方法並不是由Java源碼直接產生的,而是由編譯器自行添加的,在JDK1.5之后,標識一個類,字段或方法是編譯器自動產生的,也可以設置它們訪問標志中的ACC_SYNTHETIC標志位,其中最典型的就是Bridge Method。所有非用戶代碼生產的類,方法及字段都應當至少設置Synthetic屬性和ACC_SYNTHETIC標志位中的一項,唯一的例外是實例構造器“<init>”方法和類構造器“<clinit”方法。
Deprecated及Synthetic屬性表結構如下:

其中attribute_length數據項的值必須為0,因為沒有任何屬性值需要設置。
在JDK1.5和JDK1.6中一共增加了10項屬性,具體如下: