Code屬性
code屬性是方法的一個最重要的屬性。 因為它裡面存放的是方法的字節碼指令, 除此之外還存放了和操作數棧,局部變量相關的信息。 所有不是抽象的方法, 都必須在method_info中的attributes中有一個Code屬性。下面是Code屬性的結構, 為了更直觀的展示Code屬性和method_info的包含關系, 特意畫出了method_info:
下面依次介紹code屬性中的各個部分。
attribute_name_index指向常量池中的一個CONSTANT_Utf8_info , 這個CONSTANT_Utf8_info 中存放的是當前屬性的名字 “Code” 。
attribute_length給出了當前Code屬性的長度(不包括前六字節)。
max_stack 指定當前方法被執行引擎執行的時候, 在棧幀中需要分配的操作數棧的大小。
max_locals指定當前方法被執行引擎執行的時候, 在棧幀中需要分配的局部表量表的大小。注意, 這個數字並不是局部變量的個數, 因為根據局部變量的作用域不同, 在執行到一個局部變量以外時, 下一個局部變量可以重用上一個局部變量的空間(每個局部變量在局部變量表中占用一個或兩個Slot)。 方法中的局部變量包括方法的參數, 方法的默認參數this, 方法體中定義的變量, catch語句中的異常對象。 關於執行引擎的相關內容會在後面的博客中講到。
code_length指定該方法的字節碼的長度, class文件中每條字節碼占一個字節。
code存放字節碼指令本身, 它的長度是code_length個字節。
exception_table_length 指定異常表的大小
exception_table就是所謂的異常表, 它是對方法體中try-catch_finally的描述。 exception_table可以看做是一個數組, 每個數組項是一個exception_info結構, 一般來說每個catch塊對應一個exception_info,編譯器也可能會為當前方法生成一些exception_info。 exception_info的結構如下(為了直觀的顯示exception_info, exception_table和Code屬性的關系, 畫出了Code屬性,的話讀者就會更清楚各個數據項之間的位置關系和包含關系):
下面講解exception_info中的各個字段的意思。
start_pc是從字節碼(Code屬性中的code部分)起始處到當前異常處理器起始處的偏移量。
end_pc是從字節碼起始處到當前異常處理器末尾的偏移量。
handler_pc是指當前異常處理器用來處理異常(即catch塊)的第一條指令相對於字節碼開始處的偏移量。
catch_type是一個常量池索引, 指向常量池中的一個CONSTANT_Class_info數據項, 該數據項描述了catch塊中的異常的類型信息。這個類型必須是java.lang.Throwable的或其子類。
所以可以總結, 一個異常處理器(exception_info)的意思是: 如果偏移量從start_pc到end_pc之間的字節碼出現了catch_type描述的類型的異常, 那麼就跳轉到偏移量為handler_pc的字節碼處去執行。如果catch_type為0, 就代表不引用任何常量池項(再回顧一下, 常量池中的項是從1開始計的), 那麼這個exception_info用於實現finally子句。
我們一直在介紹Code屬性, 只不過剛才進行了一個小插曲, 介紹了Code屬性中的exception_table中的exception_info的詳細信息。 下面我們繼續介紹Code 屬性中的其他信息, 希望讀者不要被繞暈了 : )
attributes_count 表示當前Code 屬性中存在的其他屬性的個數。 現在我們知道, class中的屬性, 不僅會出現在頂層的class中, 會存在field_info中, 會存在method_info中, 甚至還會出現在屬性中。
attributes可以看做是一個數組, 裡面存放了Code屬性中的其他屬性。 Code 屬性中可以出現的其他屬性有LineNumberTable和LocalVariableTable 。 這兩個屬性會在下面介紹。
LineNumberTable屬性
LineNumberTable屬性存在於Code屬性中, 它建立了字節碼偏移量到源代碼行號之間的聯系。 這個屬性是可選的, 編譯器可以選擇不生成該屬性。下面是該屬性的結構(同樣給出了全局的位置關系,LineNumberTable在圖的右下角部分):
由於這個屬性並不是重點, 我們在此簡單的講述。
每個LineNumberTable中的line_number_table部分, 可以看做是一個數組, 數組的每項是一個line_number_info , 每個line_number_info 結構描述了一條字節碼和源碼行號的對應關系。 其中start_pc是這個line_number_info 描述的字節碼指令的偏移量, line_number是這個line_number_info 描述的字節碼指令對應的源碼中的行號。可以看出, 方法中的每條字節碼都對應一個line_number_info , 這些line_number_info 中的line_number可以指向相同的行號, 因為一行源碼可以編譯出多條字節碼。
LocalVariableTable屬性
LocalVariableTable 屬性建立了方法中的局部變量與源代碼中的局部變量之間的對應關系。 這個屬性存在於Code屬性中。 這個屬性是可選的, 編譯器可以選擇不生成這個屬性。該屬性的結構如下:(同樣給出了全局的位置關系圖,LocalVariableTable 在該圖的右下角 )
由於這個屬性相對不那麼重要, 這裡只是大概講解一下。
每個LocalVariableTable 的local_variable_table部分可以看做是一個數組, 每個數組項是一個叫做local_variable_info的結構, 該結構描述了某個局部變量的變量名和描述符, 還有和源代碼的對應關系。下面講解local_variable_info的各個部分:
start_pc是當前local_variable_info所對應的局部變量的作用域的起始字節碼偏移量;
length是當前local_variable_info所對應的局部變量的作用域的大小。 也就是從字節碼偏移量start_pc 到start_pc+length就是當前局部變量的作用域范圍;
name_index指向常量池中的一個CONSTANT_Utf8_info, 該CONSTANT_Utf8_info描述了當前局部變量的變量名;
descriptor_index指向常量池中的一個CONSTANT_Utf8_info, 該CONSTANT_Utf8_info描述了當前局部變量的描述符;
index描述了在該方法被執行時,當前局部變量在棧幀中局部變量表中的位置。
由此可知, 方法中的每個局部變量都會對應一個local_variable_info 。
Exceptions屬性
首先需要說明, Exceptions屬性不是存在於Code屬性中的, 它存在於method_info中的attributes中。 和Code屬性是平級的。 這個屬性描述的是方法聲明的可能會拋出的異常, 也就是方法定義後面的throws聲明的異常列表, 請不要和上面提到的異常處理器混淆。 異常處理器描述了方法的字節碼如何處理異常, 而Exceptions屬性描述方法可能會拋出哪些以異常。下面講解Exceptions屬性的結構(左下角為Exceptions屬性):
下面講解Exceptions屬性中的信息。
attribute_name_index和attribute_length就不多說了, 和其他屬性是一樣的。
number_of_exceptions是該方法要拋出的異常的個數。
exceptions_index_table可以看做一個數組, 這個數組中的每一項占兩個字節, 這兩個字節是對常量池的索引, 它指向一個常量池中的CONSTANT_Class_info。 這個CONSTANT_Class_info描述了一個被拋出的異常的類型。
總結
到此為止, 和方法相關的屬性就介紹完了。 這篇博客講解的內容相對比較復雜。 下面以一個實例進行驗證, 實例代碼:
package com.bjpowernode.test; public class Test { public void test() throws Exception{ int localVar = 0; try{ Class.forName("com.bjpowernode.test.Person"); }catch(ClassNotFoundException e){ throw e; }finally{ System.out.println(localVar); } } }
反編譯後的test方法部分(省略了常量池等信息):
public void test() throws java.lang.Exception; flags: ACC_PUBLIC Exceptions: throws java.lang.Exception Code: stack=2, locals=4, args_size=1 0: iconst_0 1: istore_1 2: ldc #18 // String com.bjpowernode.test.Person 4: invokestatic #20 // Method java/lang/Class.forName:(Ljava/lang/String;)Ljava/lang/Class; 7: pop 8: goto 24 11: astore_2 12: aload_2 13: athrow 14: astore_3 15: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream; 18: iload_1 19: invokevirtual #32 // Method java/io/PrintStream.println:(I)V 22: aload_3 23: athrow 24: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream; 27: iload_1 28: invokevirtual #32 // Method java/io/PrintStream.println:(I)V 31: return Exception table: from to target type 2 8 11 Class java/lang/ClassNotFoundException 2 14 14 any LineNumberTable: line 7: 0 line 11: 2 line 13: 8 line 15: 12 line 16: 14 line 17: 15 line 18: 22 line 17: 24 line 20: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lcom/bjpowernode/test/Test; 2 30 1 localVar I 12 2 2 e Ljava/lang/ClassNotFoundException;
結合上面的講解和圖解, 再分析反編譯的結果, 就一目了然了: 所有的結果是一個method_info, method_info開始處是訪問標志信息。 然後是method_info的 Exceptions屬性 , Exceptions屬性屬性下面是Code屬性, Code屬性中又包括字節碼, 異常處理器 ,LineNumberTable屬性和LocalVariableTable 屬性。
所以會直接或間接的和method_info有聯系, 最後給出一張全局圖, 這樣的話, 讀者就比較明確, 一個完整的方法, 是如何在class文件中描述的,由於考慮到復雜性, 這些屬性或其他數據項中, 對常量池的引用均未畫出: