JVM之字節碼——Class文件格式


如同講匯編必先講計算機組成原理,在開始字節碼之前,我們先了解一下JVM的主要構成。 在JVM的內部,主要由如下幾個部分構成:


    1.數據區

  • 方法區:存放類定義信息、字節碼、常量等數據,在Sun HotSpot JVM中,這塊也稱為Perm Gen。
  • 堆:創建的對象信息將放入堆中,堆內部如何實現各虛擬機各不相同,對於Sun HotSpot JVM來說又分為Young Gen和Tenured Gen,更詳細描述參見《[Java性能剖析]Sun JVM內存管理和垃圾回收 》
  • Java 棧:對於每個執行線程,會分配一個Java棧,JVM在執行過程當中,每執行一個方法,都會為方法在當前棧中增加一個棧幀,每個棧幀的信息與具體實現相 關,但一般會由3部分組成:變量區,方法參數和本地變量會放入這個位置,大小是固定的,在進行方法時會先分配好,在類定義中,會由max local來指定這塊區的大小;方法信息區,會包括當前類常量池的入口地址等信息,這塊大小也是固定的;操作棧,與Intel體系架構中的運算使用寄存器 來進行不一樣,JVM的字節碼的方法調用、運算等需要的參數,都是通過操作棧來傳遞的,在后面詳細介紹中我們會進一步了解到,在類定義中,會由max stack指定最大的操作棧。關於Java棧的更詳細描述參見《Java 棧內存介紹 》
  • 本地方法棧:對本地方法的調用,並不會使用Java棧而是使用本地方法棧,本地方法棧的組成取決於所使用的平台和操作系統
  • PC寄存器:對於每個執行線程會分配一個PC寄存器,寄存器中存放當前字節碼的執行位置

      2.類加載器子系統
      類加載器完成類的加載工作,包括查找和裝載類定義(.class)信息、連接(包括字節碼驗證、類變量內存分配和初始化、符號解析)和類初始化的過程

  • 查找和裝載類定義(.class)信息:關 於這塊的內容可以google到無數的描述文檔,JVM內部也提供了多種查找和裝載類定義的途徑,譬如從本地加載類定義、從遠程加載類定義,甚至需要的 話,我們可以對類定義進行加密在裝載的時候做處理等等,更詳細的描述也可以參見《[Tomcat源碼系列] Tomcat 類加載器結構 》
  • 連接—字節碼驗證:對於加載的類定義,JVM必須確保其是合法的,包括定義的結構是合法的、聲明的類信息(包括屬性、方法等等)是合法的、字節碼的正確性(包括確保操作碼是合法的、有合法操作棧、goto語句能夠到達一個合法的地址上等等)等等
  • 連接—類變量內存分配和初始化:在這個階段,類變量將分配內存,並設置一個合法的初始化值,譬如對象是null、數值型是0、布爾值是false等等
  • 連 接—符號解析:符號定義了類提供給外部可訪問的服務以及類需要訪問到的外部的服務,譬如類提供了方法給外部調用,或者類調用外部的方法,這些就是符號,符 號解析過程就是將符號型的描述(譬如字符串)轉換成實際的引用地址(譬如方法入口字節碼指針地址),符號解析可能是在加載時進行,也有可能推遲到實際被使 用到才去解析,但不管怎么樣,JVM應該對外提供遲解析的印象,即不管何時解析符號,總是在符號被第一次訪問的時候才會拋出異常,關於符號解析,后面會有 專門的篇章更詳細地介紹
  • 類初始化:類的初始化是在類第一次被引用到的時候進行,類初始化包括給類屬性(static)設置初始化值、調用類中的static塊代碼,在字節碼中,會有一個專門的<clinit>方法,類初始化的時候會調用這個方法,譬如如下

 

Java代碼   收藏代碼
  1. public class Test2  
  2. public static int cout =  static  
  3. for (int i= }  

   

 
     3.執行引擎:可以理解為CPU,是JVM最核心的部分。在Java虛擬機規范中,執行引擎的行為使用指令集來定義。對於每條指令,規范都詳細規定了當實現執行到該指令時應該處理什么,但卻沒有定義如何處理,具體策略交給JVM的具體實現
     在后面的篇章,將繼續介紹符號解析和字節碼

     參考: <<深入Java虛擬機>>

 


 

1、class文件格式:跨平台的基礎

     Java 字節碼可以跨不同的虛擬機在不同的平台上執行,這些字節碼按照class文件格式的規范組成了class文件,從而為Java語言跨平台執行奠定了基石; 不同的語言都可以根據class文件格式生成可以在JVM上執行的字節碼,這又給Java平台帶來了新的血液。
      class 文件是Java程序二進制的精確定義 。每一個class文件都是對一個Java 類或者接口的描述。因為有着同一的格式,  無論在何種平台上產生,也無論是在何種平台上運行,class文件的定義都能夠被Java虛擬機正確地讀取。下面先看看class文件的格式:

      一個典型的class文件分為:MagicNumber,Version,Constant_pool,Access_flag,This_class,Super_class,Interfaces,Fields,Methods 和Attributes這十個部分,用一個數據結構可以表示如下:
      ClassFile {
           u4 magic;
           u2 minor_version;
           u2 major_version;
           u2 constant_pool_count;
           cp_info constant_pool[constant_pool_count-1];
           u2 access_flags;
           u2 this_class;
           u2 super_class;
           u2 interfaces_count;
           u2 interfaces[interfaces_count];
           u2 fields_count;
           field_info fields[fields_count];
           u2 methods_count;
           method_info methods[methods_count];
           u2 attributes_count;
           attribute_info attributes[attributes_count];
}
其中u1,u2,u4,u8分別代表1字節,2字節,4字節和8字節的無符號類型整數。
 
ClassFile中的字段簡單說明如下:
1、MagicNumber:MagicNumber是用來標志class文件的, 虛擬機加載class文件的時候會先檢查這四個字節,如果不是cafe babe則虛擬機拒絕加載該文件,這樣就可以防止加載非class文件而浪費系統資源。這個字段的長度是4個字節,值是固定的cafebabe。

2、Version:version字段有2個長度都為2字節的字段組成,分別是Major Version和Minor Version,分別代表當前class文件的主版本號和次版本號。隨着Java技術的不斷發展,Java class文件格式會增加一些新的內容來支持Java語言的新特性。同時,不同的虛擬機支持的Java class文件的版本范圍是不同的,所以在加載class文件之前可以先看看該class文件是否在當前虛擬機的支持范圍之內,避免加載不支持的class文件。

3、常量池
   首先是2個字節的長度字段constant_pool_count,表明常量池包含了多少個常量。
   后面跟着就是constant_pool_count個常量,常量池里放的是字面常量和符號引用。
   字面常量主要包含文本串以及被聲明為final的常量等;符號引用包含類和接口的全局限定名,字段的名稱和描述符,方法的名稱和描述符,因為java語 言在編譯的時候沒有連接這一步,所有的引用都是運行時動態加載的,所以就需要把這些引用的信息保存在class文件里。
    這里的常量又根據具體的類型分成字符串,整形,長整型,浮點型,雙精度浮點型這幾種基本類型。
        而符號引用保存的是對應的引用的全局限定名,所以保存的是字符串。

4、access_flag 保存了當前類的訪問權限

5、this_cass  保存了當前類的全局限定名在常量池里的索引

6、super class 保存了當前類的父類的全局限定名在常量池里的索引

7、interfaces 保存了當前類實現的接口列表,包含兩部分內容:interfaces_count 和interfaces[interfaces_count]
         interfaces_count 指的是當前類實現的接口數目
        interfaces[] 是包含interfaces_count個接口的全局限定名的索引的數組

8、fields 保存了當前類的成員列表,包含兩部分的內容:fields_count 和 fields[fields_count]
     fields_count是類變量和實例變量的字段的數量總和。
     fileds[]是包含字段詳細信息的列表。
    

9、methods 保存了當前類的方法列表,包含兩部分的內容:methods_count和methods[methods_count]
     methods_count是該類或者接口顯示定義的方法的數量。
     method[]是包含方法信息的一個詳細列表。

10、attributes 包含了當前類的attributes列表,包含兩部分內容:attributes_count 和 attributes[attributes_count]
     class文件的最后一部分是屬性,它描述了該類或者接口所定義的一些屬性信息。attributes_count指的是attributes列表中包含的attribute_info的數量。
     屬性可以出現在class文件的很多地方,而不只是出現在attributes列表里。如果是attributes表里的屬性,那么它就是對整個 class文件所對應的類或者接口的描述;如果出現在fileds的某一項里,那么它就是對該字段額外信息的描述;如果出現在methods的某一項里, 那么它就是對該方法額外信息的描述。


二、手動分析class文件
     上面大致講解了一下class文件的結構,這里,我們拿一個class文件做一個簡單的分析,來驗證上面的說法。

     先看看一個簡單的類

點擊(此處)折疊或打開

  1. public class Hello{
  2.       private int test;
  3.       public int test(){
  4.             return test;
  5.         }
  6. }
     編譯之后的class文件十六進制結果如下所示,可以用UE等十六進制編輯器打開:

點擊(此處)折疊或打開

  1. ca fe ba be 00 00 00 32 00 12 0a 00 04 00 0e 09
  2. 00 03 00 0f 07 00 10 07 00 11 01 00 04 74 65 73
  3. 74 01 00 01 49 01 00 06 3c 69 6e 69 74 3e 01 00
  4. 03 28 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69
  5. 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 03
  6. 28 29 49 01 00 0a 53 6f 75 72 63 65 46 69 6c 65
  7. 01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 0c 00 07
  8. 00 08 0c 00 05 00 06 01 00 05 48 65 6c 6c 6f 01
  9. 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65
  10. 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05
  11. 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09
  12. 00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01
  13. b1 00 00 00 01 00 0a 00 00 00 06 00 01 00 00 00
  14. 01 00 01 00 05 00 0b 00 01 00 09 00 00 00 1d 00
  15. 01 00 01 00 00 00 05 2a b4 00 02 ac 00 00 00 01
  16. 00 0a 00 00 00 06 00 01 00 00 00 03 00 01 00 0c
  17. 00 00 00 02 00 0d

     接下來我們就按照class文件的格式來分析上面的一串數字,還是按照之前的順序來

1、魔數:cafebabe,這個是用來標識java class文件的,如果文件的開始不是cafebase的話,虛擬機會拒絕加載該文件。關於這個我們可以試驗一下,用16進制編輯器修改cafebabe。
運行 java Hello
會拋出ClassFormatError
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value
2868820670 in class file Hello


2、 接下來就是version字段:00 00 00 32,前兩個字節00是minor_version,后兩個字節0032是major_version字段,對應的十進制值為50,也就是說當前 class文件的主版本號為50,次版本號為0。前面講過,如果version字段不在當前虛擬機支持的范圍之內,虛擬機會拒絕加載。我們可以試一下,把 version字段改大,例如改成00 01 00 40(主版本號為64,次版本號為1)。加載該class文件,會拋出java.lang.UnsupportedClassVersionError: Hello : Unsupported major.minor version 64.1,表示當前虛擬機不支持64.1版本的class文件。


3、接下來就是constant_pool
     前面講過,常量池里存放的是字面常量和符號引用。

     1、字面常量
     字面常量分別包含String,Integer,Float,Long,Double這幾個類型,這些字段都是以二進制的方式存儲的,所以存儲的時候,只要指定類型、長度和對應的值即可。這樣的話,那這些基本的常量就可以采用如下的結構來表示:

點擊(此處)折疊或打開

  1. CONSTANT_XXX{
  2.           type;//該常量的類型
  3.           length;//該常量的長度,以字節為單位
  4.           byte[];//該常量的二進制表示,包含length個字節
  5.      }

     不過對於Integer、Float、Long和Double這些常量的長度是固定不變的,所以可以省去以節省空間;但是字符串常量的長度是不能省的。
     
     2、符號引用

     符號引用包含三種特殊的字符串,它們分別是:
     1、全局限定名 當一個常量表示類或者接口的時候,需要指定類的全局限定名,在class文件中,全局限定名的點用斜杠來代替,例如java.lang.Object的全局限定名為java/lang/Object
     2、簡單名稱 類的字段名或者方法名以簡單名稱的方式存在常量池里,例如上面Hello.test字段在class文件里會存有形如“test”的簡單名稱。
   3、描述符   除了成員變量的名稱和方法,class文件里還要存儲對應的描述符。描述符在class文件里以一種簡化的方式表示,這樣就可以減少class文件的大小。

      成員變量的描述符和方法的修飾符可以使用下面的語法來表示:

點擊(此處)折疊或打開

  1. FieldDescriptor:
  2.           FieldType

  3.      ComponentType:
  4.           FieldType

  5.       FieldType:
  6.           BaseType
  7.           ObjectType
  8.           ArrayType

  9.      BaseType:
  10.           B
  11.           C
  12.           D
  13.           F
  14.           I
  15.           J
  16.           S
  17.           Z

  18.      ObjectType:
  19.           L Classname ;

  20.      ArrayType:
  21.           [ComponentType

  22.      MethodDescriptor:
  23.           ParameterDescriptor) ReturnDescriptor

  24.      ParameterDescriptor:
  25.           FieldType

  26.      ReturnDescriptor:
  27.           FieldType
  28.           VoidDescriptor

  29.     VoidDescriptor:
  30.           V                  
     簡單來說,對於成員變量而言,它的描述符是它的類型,類型又分為:BasicType,ObjectType和ArrayType。

  •      BasicType指的是byte,char,double,float,int,long,short,boolean這幾種基本類型,它們分別用B、C、D、F、I、L、S、Z來表示。
  •      ObjectType指的是對象類型,也就是說該成員變量的類型是一個Java類,那么這種類型就用L加上Classname表示。例如一個類型為java.lang.String的變量,它的描述符為Ljava/lang/String。
  •      ArrayType指的是列表類型,也就是說這個成員變量是個數組。對於數組類型,每一個維度用一個前置的“[”來描述。例如一個定義 為"java.lang.String[][]"的變量的描述符為[[java/lang/String;對於一個定義為“int[]”的變量,它的描述 符表示為“[I”。

     對於方法而言,它的描述符是先參數列表,后返回值的方式組成。參數列表按照聲明的順序排列,每個參數的格式和成員變量的格式一樣。所有的參數放在一個括 號里面和返回值進行分隔。返回值的格式也和成員變量的格式一樣,不過返回值多了一個void類型,用“V”來表示。
     例如,對於void test()方法,它的描述符表示為“()V”,而對於java.lang.String test(int[],int index)方法,它的描述符表示為“([II)Ljava/lang/String”。
 
     這些特殊的字符串在常量池里是采取引用的方式來表示的,它們有如下的結構:  

點擊(此處)折疊或打開

  1. CONSTANT_XXX{
  2.           type;//字符串的類型
  3.           index;//對應的值的引用
  4.      }

     3、字符串的存儲方式

     在class文件里,字符串也是采用引用的方式進行存儲的,它的引用指向該字符串對應的UTF-8的一個變體的表示。

點擊(此處)折疊或打開

  1. CONSTANT_XXX{
  2.           type;//字符串的類型
  3.           index;//對應的值的引用
  4.      }
    對應的UTF-8表示的結構

點擊(此處)折疊或打開

  1. CONSTANT_UTF8{
  2.           type;
  3.           length;
  4.           value[];
  5.      }

     4、常量的基本格式
     由上面的分析可以知道,常量的結構都是以常量的類型開始,然后才是具體的數據。這樣的話,常量池里的變量都是如下的結構:

點擊(此處)折疊或打開

  1. cp_info {
  2.                u1 tag;
  3.                u1 info[];
  4.           }

     其中的tag就是常量對應的類型,主要有以下幾個值:
 后面的info[]字段,代表具體結構的數據,需要按照對應的結構來進行分析

 4、分析Hello.java的常量池

     4.1、constant_pool_count 接下來的兩個字節00 12代表常量池里包含的常量數目,也就是說這個常量池包含17個(0x0012 -1 )常量。

     4.2、constant_pool 就下來就是分析這17個常量了

        1)第一個變量 0a 00 04 00 0e
            首先,緊接着constant_pool_count的第一個字節0a(tag=10)表示這是一個CONSTANT_Methodref。CONSTANT_Methodref的結構如下:
               CONSTANT_Methodref_info {
                         u1 tag;
                         u2 class_index;
                         u2 name_and_type_index;
               }
       其中class_index表示該方法所屬的類在常量池里的索引,name_and_type_index表示該方法的名稱和類型的索引。常量池里的變量的索引從1開始。
         那么這個methodref結構的數據如下:
                    0a  //tag 表示這是一個CONSTANT_Methodref_info結構
                    00 04 //指向第4個常量所表示的類
                    00 0e  //指向第14個常量所表示的方法

       2)第二個變量
          接着是第二個常量,它的tag是09,表示這是一個CONSTANT_Fieldref的結構,它的結構如下:
                    CONSTANT_Fieldref_info {
                         u1 tag;
                         u2 class_index;
                         u2 name_and_type_index;
               }
      和上面的變量基本一致。
      09 //tag
      00 03//指向第三個常量所表示的類

    3)第三個變量 07 00 10
        tag為07表示是一個CONSTANT_Class變量,這個變量的結構如下:
     CONSTANT_Class_info {
                         u1 tag;
                         u2 name_index;
               }
除了tag字段以外,還有一個name_index是指向該變量名稱的一個索引。
               
     4)第四個變量也是一個CONSTANT_Class
     5)第五個變量 01 00 04 74 65 73 74
          tag為1,表示這是一個CONSTANT_Utf8結構,這種結構用UTF-8的一種變體來表示字符串,結構如下所示:
                    CONSTANT_Utf8_info {
                                   u1 tag;
                                   u2 length;
                                   u1 bytes[length];
                    }
           其中length表示該字符串的字節數,bytes字段包含該字符串的二進制表示。
           接着tag的兩個字節0004表示這個字符串的長度是4字節,也即是后面的74657374,表示的是字符串“test”  。
   6)接下來的8個變量都是字符串,這里就不具體分析了。
   7)第十四個常量  0c 00 07 00 08
         tag為0c,表示這是一個CONSTANT_NameAndType結構,這個結構用來描述一個方法或者成員變量。具體結構如下:
                    CONSTANT_NameAndType_info {
                              u1 tag;
                              u2 name_index;
                              u2 descriptor_index;
                    }
         name_index表示的是該變量或者方法的名稱,這里的值是0007,表示指向第7個常量,即是“<init>”。 descriptor_index指向該方法的描述符的引用,這里的值是0008,表示指向第8個常量,即是“()V”,由前面描述符的語法可知,這個方 法是一個無參的,返回值為void的方法。綜合兩個字段,可以推出這個方法是“void <init>()”。也即是指向這個NameAndType結構的Methodref的方法名為“void <init>()”,也就是說第一個常量表示的是“void <init>()”方法。

               8)第十五個常量也是一個CONSTANT_NameAndType,表示的方法名為“int test()”,第2個常量引用了這個NameAndType,所以第二個常量表示的是“int test()”方法。

               9)第16和17個常量也是字符串,可以按照前面的方法分析。
              

      4.3、完整的常量池
最后分析完的常量池如下:
          00 12  常量池的數目 18-1=17
          0a 00 04 00 0e  方法:java.lang.Ojbect void <init>()
          09 00 03 00 0f   方法 :Hello int test() 
          07 00 10  字符串:Hello
          07 00 11 字符串:java.lang.Ojbect
          01 00 04 74 65 73 74 字符串:test
          01 00 01 49  字符串:I
          01 00 06 3c 69 6e 69 74 3e 字符串:<init>
          01 00 03 28 29 56 字符串:()V
          01 00 04 43 6f 64 65 字符串:Code 
          01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 字符串:LineNumberTable 
          01 00 03 28 29 49 字符串:()I
          01 00 0a 53 6f 75 72 63 65 46 69 6c 65 字符串:SourceFile
          01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 字符串:Hello.java
          0c 00 07 00 08 NameAndType:<init> ()V
          0c 00 05 00 06 NameAndType:test I
          01 00 05 48 65 6c 6c 6f 字符串:Hello
          01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 字符串: java/lang/Object
                    

     5、access_flag  00 21
          這兩個字節的數據表示這個變量的訪問標志位,具體的取值見下表:

  0x0021=0x0001 | 0x0020 ,也就是表示當前class的access_flag是ACC_PUBLIC|ACC_SUPER。ACC_PUBLIC和代碼里的public 關鍵字相對應。ACC_SUPER表示當用invokespecial指令來調用父類的方法時需要特殊處理。
          
     6、this_class 00 03
          this_class存的是當前類的名稱在常量池里的索引,這里指向第三個常量,即是“Hello”。

     7、super_class 00 04
          super_class存的是父類的名稱在常量池里的索引,這里指向第四個常量,即是“java/lang/Object”。

     8、interfaces
          interfaces包含interfaces_count和interfaces[]兩個字段。這里interfaces_count為0(0000),所以后面的內容也對應為空。

     9、fields
               00 01 fields count//表示有一個成員變量
               00 02 00 05 00 06 00 00//成員變量的結構
               每個成員變量對應一個field_info結構:
               field_info {
                         u2 access_flags; 0002
                         u2 name_index; 0005
                         u2 descriptor_index; 0006
                         u2 attributes_count; 0000
                         attribute_info attributes[attributes_count];
               }
              access_flags為0002,即是ACC_PRIVATE
              name_index指向常量池的第五個常量,為“test”
               descriptor_index指向常量池的第6個常量為“I”
               三個字段結合起來,說明這個變量是"private int test"。
               
               接下來的是attribute字段,用來描述該變量的屬性,有無這個變量沒有附加屬性,所以attributes_count為0,attribute_info為空。
          

     10、methods
          首先是2個字節的method_count,接下來的內容是兩個method_info結構:
                    method_info {
                         u2 access_flags;
                         u2 name_index;
                         u2 descriptor_index;
                         u2 attributes_count;
                         attribute_info attributes[attributes_count];
                    }
     前三個字段和field_info一樣,可以分析出第一個方法是“public void <init>()”
                         00 01 ACC_PUBLIC
                         00 07  <init>
                         00 08  V()
     接下來是attribute字段,也即是這個方法的附加屬性,這里的attributes_count =1,也即是有一個屬性。
     每個屬性的都是一個attribute_info結構,如下所示:
                    attribute_info {
                         u2 attribute_name_index;
                         u4 attribute_length;
                         u1 info[attribute_length];
                    }
     JVM預定義了部分attribute,但是編譯器自己也可以實現自己的attribute寫入class文件里,供運行時使用。
     不同的attribute通過attribute_name_index來區分。JVM規范里對以下attribute進行了預定義:

這里的 attribute_name_index值為0009,表示指向第9個常量,即是Code。Code Attribute的作用是保存該方法的結構如所對應的字節碼,具體的結構如下所示:
          
      Code_attribute {
           u2 attribute_name_index;
           u4 attribute_length;
           u2 max_stack;
           u2 max_locals;
           u4 code_length;
           u1 code[code_length];
           u2 exception_table_length;
          
                u2 start_pc;
                u2 end_pc;
                u2 handler_pc;
                u2 catch_type;
           } exception_table[exception_table_length];
           u2 attributes_count;
           attribute_info attributes[attributes_count];
      }
     attribute_length表示attribute所包含的字節數,這里為0000001d,即是39個字節,不包含attribute_name_index和attribute_length字段。
     max_stack表示這個方法運行的任何時刻所能達到的操作數棧的最大深度,這里是0001
     max_locals表示方法執行期間創建的局部變量的數目,包含用來表示傳入的參數的局部變量,這里是0001.
     接下來的code_length表示該方法的所包含的字節碼的字節數以及具體的指令碼。
     這里的字節碼長度為00000005,即是后面的5個字節 2a b7 00 01 b1為對應的字節碼指令的指令碼。
     參照下表可以將上面的指令碼翻譯成對應的助記符:
               2a   aload_0    
               b7   invokespecial
               00  nop
               01  aconst_null
               b1  return
     這即是該方法被調用時,虛擬機所執行的字節碼

       接下來是exception_table,這里存放的是處理異常的信息。每個exception_table表項由start_pc,end_pc,handler_pc,catch_type組成。
        start_pc和end_pc表示在code數組中的從start_pc到end_pc處(包含start_pc,不包含end_pc)的指令拋出的異 常會由這個表項來處理;handler_pc表示處理異常的代碼的開始處。catch_type表示會被處理的異常類型,它指向常量池里的一個異常類。當 catch_type為0時,表示處理所有的異常,這個可以用來實現finally的功能。

     不過,這段代碼里沒有異常處理exception_table_length為0000,所以我們不做分析。

      接下來是該方法的附加屬性,attributes_count為0001,表示有一個附加屬性。
     attribute_name_index為000a,指向第十個常量,為LineNumberTable。這個屬性用來表示code數組中的字節碼和java代碼行數之間的關系。這個屬性可以用來在調試的時候定位代碼執行的行數。LineNumberTable的結構如下:
      LineNumberTable_attribute {
                u2 attribute_name_index;
                u4 attribute_length;
                u2 line_number_table_length;
                { u2 start_pc;
                u2 line_number;
           } line_number_table[line_number_table_length];
      }
     前面兩個字段分別表示這個attribute的名稱是LineNumberTable以及長度為00000006。接下來的0001表示line_number_table_length,表示line_number_table有一個表項,其中start_pc為 00 00,line_number為 00 00,表示第0行代碼從code的第0個指令碼開始。
     
     后面的內容是第二個方法,具體就不再分析了。
    
     11、attributes
          最后剩下的內容是attributes,這里的attributes表示整個class文件的附加屬性,不過結構還是和前面的attribute保持一致。
           00 01表示有一個attribute。
          attribute_name_index為000c,指向第12個常量,為SourceFile,說明這個屬性是Source Attribute。結構如下:
          SourceFile_attribute {
               u2 attribute_name_index;
               u4 attribute_length;
               u2 sourcefile_index;
          }
          attribute_length為00000002
          sourcefile_index為000d,表示指向常量池里的第13個常量,為Hello.java。
          這個屬性表明當前的class文件是從Hello.java文件編譯而來。
     
     12、最后分析完的class文件如下所示:

     ca fe ba be 魔數,cafe babe
     00 00  次版本號 00
     00 32   主版本號 50

     00 12  常量池的數目 18-1=17
     0a 00 04 00 0e  java.lang.Ojbect void <init>()
     09 00 03 00 0f   Hello int 
     07 00 10  Hello
     07 00 11  java.lang.Ojbect
     01 00 04 74 65 73 74 test
     01 00 01 49  I
     01 00 06 3c 69 6e 69 74 3e <init>
     01 00 03 28 29 56 ()V
     01 00 04 43 6f 64 65 Code 
     01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 LineNumberTable 
     01 00 03 28 29 49 ()I
     01 00 0a 53 6f 75 72 63 65 46 69 6c 65 SourceFile
     01 00 0a 48 65 6c 6c 6f 2e 6a 61 76 61 Hello.java
     0c 00 07 00 08 <init> ()V
     0c 00 05 00 06 test I
     01 00 05 48 65 6c 6c 6f Hello
     01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74  java/lang/Object

     00 21 access_flag
     00 03 this_class
     00 04 super_class
     00 00 interfaces_count
     00 01 fields count
     00 02 00 05 00 06 00 00
     
     00 02 method count
     00 01 00 07 00 08 00 01 <init>()
     00 09 00 00 00 1d 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00  
     00 01 00 0a 00 00 00 06 00 01 00 00 00
     01
     00 01 00 05 00 0b 00 01 int test()
     00 09 00 00 00 1d 00
     01 00 01 00 00 00 05 2a b4 00 02 ac 00 00 00 01
     00 0a 00 00 00 06 00 01 00 00 00 03

     00 01 00 0c SourceFile
     00 00 00 02
     00 0d Hello.java


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM