《Java虛擬機原理圖解》1.5、 class文件中的方法表集合--method方法在class文件中是怎樣組織的

參考網址:http://blog.csdn.net/luanlouis/article/details/41113695


0. 前言

         瞭解JVM虛擬機原理是每一個Java程序員修煉的必經之路。但是由於JVM虛擬機中有很多的東西講述的比較寬泛,在當前接觸到的關於JVM虛擬機原理的教程或者博客中,絕大部分都是充斥的文字性的描述,很難給人以形象化的認知,看完之後感覺還是稀裏糊塗的。

         感於以上的種種,我打算把我在學習JVM虛擬機的過程中學到的東西,結合自己的理解,總結成《Java虛擬機原理圖解》 這個系列,以圖解的形式,將抽象的JVM虛擬機的知識具體化,希望能夠對想了解Java虛擬機原理的的Java程序員 提供點幫助。

讀完本文,你將會學到:

1、類中定義的method方法是如何在class文件中組織的

2、method方法的表示-方法表集合在class文件的什麼位置

3、類中的method方法的實現代碼---即機器碼指令存放到哪了,並初步瞭解機器指令

4. 爲什麼沒有在類中定義自己的構造函數,卻可以使用new ClassName()構造函數創建對象

5. IDE代碼提示功能的基本原理



1.概述

      方法表集合是指由若干個方法表(method_info)組成的集合。對於在類中定義的若干個,經過JVM編譯成class文件後,會將相應的method方法信息組織到一個叫做方法表集合的結構中,字段表集合是一個類數組結構,如下圖所示:


2. method方法的描述-方法表集合在class文件中的位置

           method方法的描述-方法表集合緊跟在字段表集合的後面(想了解字段表集合的讀者可以點擊我查看),如下圖所示:

      


接下來讓我們看看Method_info 結構體是怎麼組織method方法信息的:

3. 一個類中的method方法應該包含哪些信息?----method_info結構體的定義

           對於一個方法的表示,我們根據我們可以概括的信息如下所示:

      

      實際上JVM還會對method方法的描述添加其他信息,我們將在後面詳細討論。如上圖中的method_info結構體的定義,該結構體的定義跟描述field字段 的field_info結構體的結構幾乎完全一致,如下圖所示

        

    方法表的結構體由:訪問標誌(access_flags)、名稱索引(name_index)、描述索引(descriptor_index)、屬性表(attribute_info)集合組成。

訪問標誌(access_flags):

method_info結構體最前面的兩個字節表示的訪問標誌(access_flags),記錄這這個方法的作用域、靜態or非靜態、可變性、是否可同步、是否本地方法、是否抽象等信息,實際上不止這些信息,我們後面會詳細介紹訪問標誌這兩個字節的每一位具體表示什麼意思。

名稱索引(name_index):

緊跟在訪問標誌(access_flags)後面的兩個字節稱爲名稱索引,這兩個字節中的值指向了常量池中的某一個常量池項,這個方法的名稱以UTF-8格式的字符串存儲在這個常量池項中。如public void methodName(),很顯然,“methodName”則表示着這個方法的名稱,那麼在常量池中會有一個CONSTANT_Utf8_info格式的常量池項,裏面存儲着“methodName”字符串,而mehodName()方法的方法表中的名稱索引則指向了這個常量池項。

描述索引(descriptor_index):

描述索引表示的是這個方法的特徵或者說是簽名一個方法會有若干個參數和返回值,而若干個參數的數據類型和返回值的數據類型構成了這個方法的描述,其基本格式爲:     (參數數據類型描述列表)返回值數據類型   。我們將在後面繼續討論。

屬性表(attribute_info)集合:

    這個屬性表集合非常重要,方法的實現被JVM編譯成JVM的機器碼指令機器碼指令就存放在一個Code類型的屬性表中;如果方法聲明要拋出異常,那麼異常信息會在一個Exceptions類型的屬性表中予以展現。Code類型的屬性表可以說是非常複雜的內容,也是本文最難的地方。


接下來,我們將一一擊破它們,看看它們到底是怎麼表示的。

4. 訪問標誌(access_flags)---記錄着method方法的訪問信息

訪問標誌(access_flags)共佔有2 個字節,分爲 16 位,這 16位 表示的含義如下所示:

舉例:某個類中定義瞭如下方法:

[java] view plain copy print?

  1. public static synchronized final void greeting(){  

  2. }  

greeting()方法的修飾符有:public、static、synchronized、final 這幾個修飾符修飾,那麼相對應地,greeting()方法的訪問標誌中的ACC_PUBLIC、ACC_STATIC、ACC_SYNCHRONIZED、ACC_FINAL標誌位都應該是1,即:


從上圖中可以看出訪問標誌的值應該是二進制00000000 00111001,即十六進制0x0039。我們將在文章的最後一個例子中證實這裏點。

5. 名稱索引和描述符索引----一個方法的簽名

    緊接着訪問標誌(access_flags後面的兩個字節,叫做名稱索引(name_index),這兩個字節中的值是指向了常量池中某個常量池項的索引,該常量池項表示這這個方法名稱的字符串。

    方法描述符索引(descrptor_index)是緊跟在名稱索引後面的兩個字節,這兩個字節中的值跟名稱索引中的值性質一樣,都是指向了常量池中的某個常量池項。這兩個字節中的指向的常量池項,是表示了方法描述符的字符串

   所謂的方法描述符,實質上就是指用一個什麼樣的字符串來描述一個方法,方法描述符的組成如下圖所示:


               關於不同的數據類型的描述符是怎樣的,我已經在《Java虛擬機原理圖解》1.4 class文件中的字段表集合--field字段在class文件中是怎樣組織的  第五部分字段的數據類型表示和字段名稱表示 進行過詳細的闡釋,感興趣的讀者可以前去查看。

          

           舉例:對於如下定義的的greeting()方法,我們來看一下對應的method_info結構體中的名稱索引和描述符索引信息是怎樣組織的。


[java] view plain copy print?

  1. public static synchronized final void greeting(){  

  2. }  

              如下圖所示,method_info結構體的名稱索引中存儲了一個索引值x,指向了常量池中的第x項,第 x項表示的是字符串"greeting",即表示該方法名稱是"greeting";描述符索引中的y 值指向了常量池的第y項,該項表示字符串"()V",即表示該方法沒有參數,返回值是void類型。




6.屬性表集合--記錄方法的機器指令和拋出異常等信息

         屬性表集合記錄了某個方法的一些屬性信息,這些信息包括:

  • 這個方法的代碼實現,方法的可執行的機器指令

  • 這個方法聲明的要拋出的異常信息

  • 這個方法是否被@deprecated註解表示

  • 這個方法是否是編譯器自動生成的

       屬性表(attribute_info)結構體的一般結構如下所示:

                

6.1 Code類型的屬性表--method方法中的機器指令的信息

     Code類型的屬性表(attribute_info)可以說是class文件中最爲重要的部分,因爲它包含的是JVM可以運行的機器碼指令,JVM能夠運行這個類,就是從這個屬性中取出機器碼的。除了要執行的機器碼,它還包含了一些其他信息,如下所示:

Code屬性表的組成部分:

機器指令----code:

目前的JVM使用一個字節表示機器操作碼,即對JVM底層而言,它能表示的機器操作碼不多於2的 8 次方,即 256個。class文件中的機器指令部分是class文件中最重要的部分,並且非常複雜,本文的重點不止介紹它,我將專門在一片博文中討論它,敬請期待。

異常處理跳轉信息---exception_table:

如果代碼中出現了try{}catch{}塊,那麼try{}塊內的機器指令的地址範圍記錄下來,並且記錄對應的catch{}塊中的起始機器指令地址,當運行時在try塊中有異常拋出的話,JVM會將catch{}塊對應懂得其實機器指令地址傳遞給PC寄存器,從而實現指令跳轉;

Java源碼行號和機器指令的對應關係---LineNumberTable屬性表:

編譯器在將java源碼編譯成class文件時,會將源碼中的語句行號跟編譯好的機器指令關聯起來,這樣的class文件加載到內存中並運行時,如果拋出異常,JVM可以根據這個對應關係,拋出異常信息,告訴我們我們的源碼的多少行有問題,方便我們定位問題。這個信息不是運行時必不可少的信息,但是默認情況下,編譯器會生成這一項信息,如果你項取消這一信息,你可以使用-g:none 或-g:lines來取消或者要求設置這一項信息。如果使用了-g:none來生成class文件,class文件中將不會有LineNumberTable屬性表,造成的影響就是 將來如果代碼報錯,將無法定位錯誤信息報錯的行,並且如果項調試代碼,將不能在此類中打斷點(因爲沒有指定行號。)

局部變量表描述信息----LocalVariableTable屬性表:

局部變量表信息會記錄棧幀局部變量表中的變量和java源碼中定義的變量之間的關係,這個信息不是運行時必須的屬性,默認情況下不會生成到class文件中。你可以根據javac指令的-g:none或者-g:vars選項來取消或者設置這一項信息。

它有什麼作用呢?  當我們使用IDE進行開發時,最喜歡的莫過於它們的代碼提示功能了。如果在項目中引用到了第三方的jar包,而第三方的包中的class文件中有無LocalVariableTable屬性表的區別如下所示:

Code屬性表結構體的解釋:

1.attribute_name_index,屬性名稱索引,佔有2個字節,其內的值指向了常量池中的某一項,該項表示字符串“Code”;
2. attribute_length,屬性長度,佔有 4個字節,其內的值表示後面有多少個字節是屬於此Code屬性表的;
3. max_stack,操作數棧深度的最大值,佔有 2 個字節,在方法執行的任意時刻,操作數棧都不應該超過這個值,虛擬機的運行的時候,會根據這個值來設置該方法對應的棧幀(Stack Frame)中的操作數棧的深度;
4. max_locals,最大局部變量數目,佔有 2個字節,其內的值表示局部變量表所需要的存儲空間大小;
5. code_length,機器指令長度,佔有 4 個字節,表示跟在其後的多少個字節表示的是機器指令;
6. code,機器指令區域,該區域佔有的字節數目由 code_length中的值決定。JVM最底層的要執行的機器指令就存儲在這裏;
7. exception_table_length,顯式異常表長度,佔有2個字節,如果在方法代碼中出現了try{} catch()形式的結構,該值不會爲空,緊跟其後會跟着若干個exception_table結構體,以表示異常捕獲情況;
8. exception_table顯式異常表,佔有8 個字節,start_pc,end_pc,handler_pc中的值都表示的是PC計數器中的指令地址。exception_table表示的意思是:如果字節碼從第start_pc行到第end_pc行之間出現了catch_type所描述的異常類型,那麼將跳轉到handler_pc行繼續處理。
9. attribute_count,屬性計數器,佔有 2 個字節,表示Code屬性表的其他屬性的數目
10. attribute_info,表示Code屬性表具有的屬性表,它主要分爲兩個類型的屬性表:“LineNumberTable”類型和“LocalVariableTable”類型。
LineNumberTable”類型的屬性表記錄着Java源碼和機器指令之間的對應關係
LocalVariableTable”類型的屬性表記錄着局部變量描述

舉例:

    如下定義Simple類,使用javac -g:none Simple.java 編譯出Simple.class 文件,並使用javap -v Simple > Simple.txt 查看反編譯的信息,然後看Simple.class文件中的方法表集合是怎樣組織的:

[java] view plain copy print?

  1. package com.louis.jvm;  

  2.   

  3. public class Simple {  

  4.   

  5.     public static synchronized final void greeting(){  

  6.         int a = 10;  

  7.     }  

  8. }  

1. Simple.class文件組織信息如下所示:


如上所示,方法表集合使用了藍色線段圈了起來。

請注意:方法表集合的頭兩個字節,即方法表計數器(method_count的值是0x0002,它表示該類中有個方法。細心的讀者會注意到,我們的Simple.java中就定義了一個greeting()方法,爲什麼class文件中會顯示有兩個方法呢??

JVM爲沒有顯式定義實例化構造方法的類,自動生成默認的實例化構造方法"<init>()"

      這是因爲:如果我們在類中沒有定義實例化構造方法,JVM編譯器在將源碼編譯成class文件時,會自動地爲這個類添加一個不帶參數的實例化構造方法,這種添加是字節碼級別的,JVM對所有的類實例化構造方法名採用了相同的名稱:“<init>”。如果我們顯式地如下定義Simple()構造函數,這個類編譯出來的class文件和上面的不帶Simple構造方法的Simple類生成的class文件是完全相同的:

[java] view plain copy print?

  1. package com.louis.jvm;  

  2.   

  3. public class Simple {  

  4.   

  5.     public Simple(){  

  6.         super();  

  7.     }  

  8.       

  9.     public static synchronized final void greeting(){  

  10.         int a = 10;  

  11.     }  

  12. }  

    這也就是爲什麼雖然我們顯式地在類中定義類構造方法,卻可以使用 new ClassName()創建實例了。

    除了實例化構造方法,JVM還會在特殊的情況下生成一個叫類構造方法"<cinit>()"。如果我們在類中使用到了static修飾的代碼塊,那麼,JVM會在class文件中生成一個“<cinit>()”構造方法。關於它們的具體細節,我將在後續的文章中詳細討論,在這裏就不展開了。

Simple.classz中出現了兩個方法表,分別代表構造方法<init>() greeting()方法,現在讓我們分別來討論這兩個方法:


2.  Simple.class 中的<init>() 方法:


 解釋:

 1. 方法訪問標誌(access_flags): 佔有 2個字節,值爲0x0001,即標誌位的第 16 位爲 1,所以該<init>()方法的修飾符是:ACC_PUBLIC;

 2. 名稱索引(name_index): 佔有 2 個字節,值爲 0x0004,指向常量池的第 4項,該項表示字符串“<init>”,即該方法的名稱是“<init>”;

 3.描述符索引(descriptor_index): 佔有 2 個字節,值爲0x0005,指向常量池的第 5 項,該項表示字符串“()V”,即表示該方法不帶參數,並且無返回值(構造函數確實也沒有返回值);

4. 屬性計數器(attribute_count): 佔有 2 個字節,值爲0x0001,表示該方法表中含有一個屬性表,後面會緊跟着一個屬性表;

5. 屬性表的名稱索引(attribute_name_index):佔有 2 個字節,值爲0x0006,指向常量池中的第6 項,該項表示字符串“Code”,表示這個屬性表是Code類型的屬性表;

6. 屬性長度(attribute_length:佔有4個字節,值爲0x0000 0011,即十進制的 17,表明後續的 17 個字節可以表示這個Code屬性表的屬性信息;

7. 操作數棧的最大深度(max_stack:佔有2個字節,值爲0x0001,表示棧幀中操作數棧的最大深度是1

8. 局部變量表的最大容量(max_variable:佔有2個字節,值爲0x0001JVM在調用該方法時,根據這個值設置棧幀中的局部變量表的大小;

9. 機器指令數目(code_length):佔有4個字節,值爲0x0000 0005,表示後續的個字節 0x2A 、0xB7、 0x00、0x01、0xB1表示機器指令;

10. 機器指令集(code[code_length]):這裏共有  5個字節,值爲0x2A 、0xB7、 0x00、0x01、0xB1

機器指令集的解析

 JVM的機器指令中的操作碼(Opcode)規定只用個字節表示,所以JVM的指令最多不超過256 個。

 現在我們將上述的 5個字節按字節分析這些機器指令都是幹什麼用的:

    第一個字節 0x2A,查詢Java 虛擬機規範中關於操作碼的解釋,0x2A 對應的操作是"aload_0",作用是將第一個引用類型局部變量推送至棧頂;

    第二個字節 0xB7,0xB7 對應的操作是:"invokespecial",作用是調用超類構造方法、實例初始化方法或私有方法;帶有2個字節的參數,即後面的 0x00、0x01 是它的參數,這個參數是某個常量池中的索引,指向了常量池的第一項,該項表示一個方法引用項CONSTANT_Methodref_info結構體,表示java.lang.Object 類中的<init>()方法,即  java/lang/Object."<init>":()V。這條指令的意思就是調用父類Object的構造方法<init>()

     接着第5個字符是0xB1 ,對應操作是:“Ireturn”,作用是表示無返回值的方法返回,結束方法調用,這條語句放在方法的機器碼最後,表示方法結束調用,返回。

我們可以使用javap -v Simple > Simple.txt,查看反編譯信息是怎樣顯示這一信息的:

注:關於Java機器指令的說明和解釋,讀者可以自行參考《Java Vritual Machine Specification --for Java  SE  7》《Java虛擬機規範--Java SE 7》,在附錄表中有詳細的介紹。

JVM的機器指令集是很複雜的一部分,它的運行還涉及到它的體系結構的設計,在這裏不好展開,我將在後續的章節中專門討論JVM虛擬機的機器指令問題,敬請期待。

11. 顯式異常表集合(exception_table_count: 佔有2 個字節,值爲0x0000,表示方法中沒有需要處理的異常信息;

12. Code屬性表的屬性表集合(attribute_count: 佔有2 個字節,值爲0x0000,表示它沒有其他的屬性表集合,因爲我們使用了-g:none 禁止編譯器生成Code屬性表 LineNumberTable 和LocalVariableTable;


B.  Simple.class 中的greeting() 方法:



 解釋:

 1. 方法訪問標誌(access_flags): 佔有 2個字節,值爲 0x0039 ,即二進制的00000000 00111001,即標誌位的第11、12、13、16位爲1,根據上面講的方法標誌位的表示,可以得到該greeting()方法的修飾符有:ACC_SYNCHRONIZED、ACC_FINAL、ACC_STATIC、ACC_PUBLIC;

 2. 名稱索引(name_index): 佔有 2 個字節,值爲 0x0007,指向常量池的第 7 項,該項表示字符串“greeting”,即該方法的名稱是“greeting”;

 3. 描述符索引(descriptor_index): 佔有 2 個字節,值爲0x0005,指向常量池的第 5 項,該項表示字符串“()V”,即表示該方法不帶參數,並且無返回值;

4. 屬性計數器(attribute_count): 佔有 2 個字節,值爲0x0001,表示該方法表中含有一個屬性表,後面會緊跟着一個屬性表;

5.屬性表的名稱索引(attribute_name_index):佔有 2 個字節,值爲0x0006,指向常量池中的第項,該項表示字符串“Code”,表示這個屬性表是Code類型的屬性表;

6. 屬性長度(attribute_length:佔有4個字節,值爲0x0000 0010,即十進制的16,表明後續的16個字節可以表示這個Code屬性表的屬性信息;

7. 操作數棧的最大深度(max_stack:佔有2個字節,值爲0x0001,表示棧幀中操作數棧的最大深度是1

8. 局部變量表的最大容量(max_variable:佔有2個字節,值爲0x0001JVM在調用該方法時,根據這個值設置棧幀中的局部變量表的大小;

9. 機器指令數目(code_length):佔有個字節,值爲0x0000 0004,表示後續的4個字節0x10、 0x0A、 0x3B、0xB1的是表示機器指令;

10.機器指令集(code[code_length]):這裏共有個字節,值爲0x10、 0x0A、 0x3B、0xB1 ;

機器指令集的解析

第一個字節 0x10,查詢Java虛擬機規範中關於操作碼的解釋,0x10 對應的操作是"bipush"," 作用是將單字節的常量值(-128~127) 推送至棧頂,它要求一個參數,後面的 0x0A 即是需要推送到棧頂的單字節,注意這裏的 0x0A 是16進制,就是我們在代碼裏寫的"a=10"中的10。

第三個字節"3B",“3B”對應的操作是:"istore_0",作用是將棧頂int 型數值存入第一個局部變量。我們在greeting() 方法中就聲明瞭一個局部變量a,JVM的運行的時候,將這個局部變量a解析,並放置到局部變量表中的第一個位置;上述的0x10 0x0A 指令已經將0x0A 推送到了棧頂了,然後 0x3B指令便將棧頂的0x0A 取出,賦值給局部變量表中的第一個參數,即局部變量a,

這樣就完成了對局部變量a的賦值;

接着第4個字符是0xB1 ,對應操作是:“Ireturn”,作用是表示無返回值的方法返回,結束方法調用,這條語句放在方法的機器碼最後,表示方法結束調用,返回。

我們可以使用javap -v Simple > Simple.txt,查看反編譯信息是怎樣顯示這一信息的:


注:關於Java機器指令的說明和解釋,讀者可以自行參考《Java Vritual Machine Specification --for Java  SE  7》《Java虛擬機規範--Java SE 7》,在附錄表中有詳細的介紹。

JVM的機器指令集是很複雜的一部分,它的運行還涉及到它的體系結構的設計,在這裏不好展開,我將在後續的章節中專門討論JVM虛擬機的機器指令問題,敬請期待。


11. 顯式異常表集合(exception_table_count: 佔有2 個字節,值爲0x0000,表示方法中沒有需要處理的異常信息;

12. Code屬性表的屬性表集合(attribute_count: 佔有2 個字節,值爲0x0000,表示它沒有其他的屬性表集合,因爲我們使用了-g:none 禁止編譯器生成Code屬性表 LineNumberTable 和LocalVariableTable;



6.2 Exceptions類型的屬性表----method方法聲明的要拋出的異常信息

有些方法在定義的時候,會聲明該方法會拋出什麼類型的異常,如下定義一個Interface接口,它聲明瞭sayHello()方法,拋出Exception異常:

[java] view plain copy print?

  1. package com.louis.jvm;  

  2.   

  3. public interface Interface {  

  4.   

  5.     public  void sayHello() throws Exception;  

  6. }  

現在讓我們看一下Exceptions類型的屬性表(attribute_info)結構體是怎樣組織的:

如上圖所示,Exceptions類型的屬性表(attribute_info)結構體由一下元素組成:

屬性名稱索引(attribute_name_index):佔有 2個字節,其中的值指向了常量池中的表示"Exceptions"字符串的常量池項;

屬性長度(attribute_length):它比較特殊,佔有4個字節,它的值表示跟在其後面多少個字節表示異常信息;

異常數量(number_of_exceptions):佔有2 個字節,它的值表示方法聲明拋出了多少個異常,即表示跟在其後有多少個異常名稱索引

異常名稱索引(exceptions_index_table):佔有2個字節,它的值指向了常量池中的某一項,該項是一個CONSTANT_Class_info類型的項,表示這個異常的完全限定名稱;


Exceptions類型的屬性表的長度計算

如果某個方法定義中,沒有聲明拋出異常,那麼,表示該方法的方法表(method_info)結構體中的屬性表集合中不會有Exceptions類型的屬性表;換句話說,如果方法聲明瞭要拋出的異常,方法表(method_info)結構體中的屬性表集合中必然會有Exceptions類型的屬性表,並且該屬性表中的異常數量不小於1

我們假設異常數量中的值爲 N,那麼後面的異常名稱索引的數量就爲N,它們總共佔有的字節數爲N*2,而異常數量佔有2個字節,那麼將有下面的這個關係式:

       屬性長度(attribute_length)中的值= 2  + 2*異常數量(number_of_exceptions)中的值

       Exceptions類型的屬性表(attribute_info的長度=2+4+屬性長度(attribute_length)中的值

舉例:

將上面定義的Interface接口類編譯成class文件,然後我們查看Interface.class文件,找出方法表集合所在位置和相應的數據,並輔助javap -v  Inerface 查看常量池信息,如下圖所示:        

      由於sayHello()方法是在的Interface接口類中聲明的,它沒有被實現,所以它對應的方法表(method_info)結構體中的屬性表集合沒有Code類型的屬性表

注:

1. 方法計數器(methods_count中的值爲0x0001,表明其後的方法表(method_info)就一個,即我們就定義了一個方法,其後會緊跟着一個方法表(method_info)結構體;

2. 方法的訪問標誌(access_flags的值是0x0401,二進制是00000100 00000001,第6位和第16位是1,對應上面的標誌位信息,可以得出它的訪問標誌符有:ACC_ABSTRACT、ACC_PUBLIC。細心的讀者可能會發現,在上面聲明的sayHello()方法中並沒有聲明爲abstract類型啊。確實如此,這是因爲編譯器對於接口內聲明的方法自動加上ACC_ABSTRACT標誌

3. 名稱索引(name_index中的值爲0x00050x0005指向了常量池的第5項,第五項表示的字符串爲“sayHello”,即表示的方法名稱是sayHello

4. 描述符索引(descriptor_index)中的值爲0x0006,0x0006指向了常量池中的第6項,第6項表示的字符串爲“()V” 表示這個方法的無入參,返回值爲void類型

5. 屬性表計數器(attribute_count)中的值爲0x0001,表示後面的屬性表的個數就1個,後面緊跟着一個attribute_info結構體;

6. 屬性表(attribute_info中的屬性名稱索引(attribute_name_index)中的值爲0x00070x0007指向了常量池中的第7 項,第 7項指向字符串“Exceptions”,即表示該屬性表表示的異常信息;

7. 屬性長度(attribute_length中的值爲:0x00000004,即後續的4個字節將會被解析成屬性值;

8. 異常數量(number_of_exceptions中的值爲0x0001,表示這個方法聲明拋出的異常個數是1個;

9.異常名稱索引(exception_index_table)中的值爲0x0008,指向了常量池中的第8項,第8項表示的是CONSTANT_Class_info類型的常量池項,表示“java/lang/Exception”,即表示此方法拋出了java.lang.Exception異常。



7.  IDE代碼提示功能實現的基本原理

         現在對於企業級的開發,開發者們越來越依賴IDE如Intellij IDEA、Eclipse、MyEclipse、NetBeans等,利用他們提供的高級功能,可以極大地提高編碼的速度和效率。

       每個IDE都提供了代碼提示功能,它們實現的基本原理其實就是IDE針對它們項目下的包中所有的class文件進行建模,解析出它們的方法信息,當我們一定的條件時,IDE會自動地將合適條件的方法列表展示給開發者,供開發者使用。

在上面將Code屬性表的時候也講了,如果編譯的第三方包,沒有LocalVariableTable屬性表信息,IDE的提示信息會稍有不同:




8.  寫在後面

       以上就是Class文件的方法表集合的全部內容。

      讀者可能覺得本文關於方法表的Code屬性表討論的不夠深入,在討論Code屬性表的時候,我簡單介紹了它的兩個屬性表LineNumberTable 和LocalVariableTable這兩個在有什麼實際作用,但是沒有詳細第介紹它們,並且在列舉的例子中,刻意地使用了  -g:none 選項 ,以使生成的class文件沒有這兩項信息,這麼做是因爲Code 屬性太過複雜,而本文主要是想讓讀者瞭解的是 方法表集合,所以就生成了最精簡的Code屬性表,以減少讀者的負擔。

    接下來的一篇文章,我打算專門來討論Code屬性表,揭開Code屬性表的所有祕密,敬請關注~~

     本文還引出了一個需要討論的話題:就是Code屬性表中的機器指令,機器指令的運行要依賴於JVM體系結構的設計機制,理解機器指令的運行機制,這將是根非常非常難啃的骨頭驚恐.......

      




作者給讀者的一些建議:

1. 由於class文件的信息繁雜,爲了減少class文件的複雜程度,本文列舉的例子都是針對特定情況精簡的,儘量減少不必要的學習障礙,所以作者希望讀者好好研究一下本文所列舉的例子,讀者最好自己動手,自己編譯源代碼,生成class文件,並查看class文件中的信息,然後逐字節分析,如果你真這麼做了,你會發現,class文件的組織格式原來真的很簡單..

2. 閱讀了本文,並不能保證讓你完全、系統地掌握class文件組織形式。如果你想全面系統地掌握它,你還需要閱讀:

    《Java Vritual Machine Specification _J2SE 7》(Java虛擬機規範 Java SE7 版)(可點擊下載)

    《深入理解Java虛擬機:JVM高級特性與最佳實踐》,周志明(可點擊下載)

    這兩本書很系統地介紹了class文件的組織形式,如果你覺得這兩本書中有的部分將的太抽象,不好理解,那麼你再回頭看看本文,本文能給你一個形象化和直觀化的解釋。


衷心希望《Java虛擬機原理圖解》這個專欄能夠幫助到廣大的Java 程序員們!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章