如何閱讀JAVA 字節碼(一)

  在閱讀JAVA字節碼之前,須要回憶一下JVM的結構:
javascript


   Java字節碼的信息主要在Java棧中間體現,下圖來自網絡,描述了java棧的基本結構:

   值得注意的是方法區,在Java虛擬機中,方法區(Method Area)是可供各條線程共享的運行時內存區域。方法 區與傳統語言中的編譯代碼儲存區(Storage Area Of Compiled Code)或者操做系統進程的正文段(TextSegment)的做用很是相似,它存儲了每個類的結構信息,例如運行時常量池(RuntimeConstantPool)、字段和方法數據、構造函數和普通方法的字節碼內容、還包括一些在類、實例、接口初始化時用到的特殊方法。

   每個方法從調用開始到執行完成的過程,就對應着一個棧幀在虛擬機棧裏面從入棧到出棧的過程。

   對於執行引擎來講,活動線程中,只有棧頂的棧幀是有效的,稱爲當前棧幀,這個棧幀所關聯的方法稱爲當前方法。執行引擎所運行的全部字節碼指令都只針對當前棧幀進行操做。

   關於棧幀內部4個區域的含義,直接引用了JAVA虛擬機規範當中的內容。

局部變量表:

  每一個棧幀內部都包含一組稱爲局部變量表(LocalVariables)的變量列表。棧幀中局部變量表的長度由編譯期決定,而且存儲於類和接口的二進制表示之中,既經過方法的Code屬性保存及提供給棧幀使用。

  一個局部變量能夠保存一個類型爲 booleanbytecharshortfloatreferencereturnAddress的數據,兩個局部變量能夠保存一個類型爲 longdouble的數據。

  局部變量使用索引來進行定位訪問,第一個局部變量的索引值爲零,局部變量的索引值是從零 至小於局部變量表最大容量的全部整數。

  longdouble類型的數據佔用兩個連續的局部變量,這兩種類型的數據值採用兩個局部變 量之中較小的索引值來定位。例如咱們講一個 double 類型的值存儲在索引值爲 n 的局部變量中, 實際上的意思是索引值爲 nn+1 的兩個局部變量都用來存儲這個值。索引值爲n+1的局部變量是沒法直接讀取的,可是可能會被寫入,不過若是進行了這種操做,就將會致使局部變量n的內容失效掉。

  上文中說起的局部變量 n 的 n 值並不要求必定是偶數,Java 虛擬機也不要求 double 和 long 類型數據採用 64 位對其的方式存放在連續的局部變量中。虛擬機實現者能夠自由地選擇適當的方 式,經過兩個局部變量來存儲一個 double 或 long 類型的值。

  Java 虛擬機使用局部變量表來完成方法調用時的參數傳遞,當一個方法被調用的時候,它的 參數將會傳遞至從 0 開始的連續的局部變量表位置上。特別地,當一個實例方法被調用的時候,第0個局部變量必定是用來存儲被調用的實例方法所在的對象的引用(即 Java 語言中的「this」 關鍵字)。後續的其餘參數將會傳遞至從 1開始的連續的局部變量表位置上。

  說白了,局部變量表就是存儲方法參數和局部變量的地方。java

操做數棧

  每個棧幀內部都包含一個稱爲操做數棧(Operand Stack)的後進先出 (Last-In-First-Out,LIFO)棧。棧幀中操做數棧的長度由編譯期決定,而且存儲於類和接 口的二進制表示之中,既經過方法的 Code 屬性保存及提供給棧幀使用。

在上下文明確,不會產生誤解的前提下,咱們常常把「當前棧幀的操做數棧」直接簡稱爲「操 做數棧」。
  操做數棧所屬的棧幀在剛剛被建立的時候,操做數棧是空的。
編程

Java 虛擬機提供一些字節碼指 令來從局部變量表或者對象實例的字段中複製常量或變量值到操做數棧中,也提供了一些指令用於 從操做數棧取走數據、操做數據和把操做結果從新入棧。在方法調用的時候,操做數棧也用來準備 調用方法的參數以及接收方法返回結果 bootstrap

  舉個例子,iadd 字節碼指令的做用是將兩個 int 類型的數值相加,它要求在執行的以前操做 數棧的棧頂已經存在兩個由前面其餘指令放入的 int 型數值。在 iadd 指令執行時,2 個 int 值 從操做棧中出棧,相加求和,而後將求和結果從新入棧。在操做數棧中,一項運算常由多個子運算 (Subcomputations)嵌套進行,一個子運算過程的結果能夠被其餘外圍運算所使用。

  每個操做數棧的成員(Entry)能夠保存一個 Java 虛擬機中定義的任意數據類型的值,包 括 long 和 double 類型。
  在操做數棧中的數據必須被正確地操做,這裏正確操做是指對操做數棧的操做必須與操做數棧 棧頂的數據類型相匹配,例如不能夠入棧兩個 int 類型的數據,而後看成 long 類型去操做他們, 或者入棧兩個 float 類型的數據,而後使用 iadd 指令去對它們進行求和。有一小部分 Java 虛 擬機指令(例如 dup 和 swap 指令)能夠不關注操做數的具體數據類型,把全部在運行時數據區 中的數據看成裸類型(Raw Type)數據來操做,這些指令不能夠用來修改數據,也不能夠拆散那 些本來不可拆分的數據,這些操做的正確性將會經過 Class 文件的校驗過程來強制保障。
  在任意時刻,操做數棧都會有一個肯定的棧深度,一個 long 或者 double 類型的數據會佔用 兩個單位的棧深度,其餘數據類型則會佔用一個單位深度。數組

動態連接

  每個棧幀內部都包含一個指向運行時常量池的引用來支持當前方法 的代碼實現動態連接(Dynamic Linking)。在 Class 文件裏面,描述一個方法調用了其餘方法, 或者訪問其成員變量是經過符號引用(Symbolic Reference)來表示的,動態連接的做用就是 將這些符號引用所表示的方法轉換爲實際方法的直接引用。類加載的過程當中將要解析掉還沒有被解析 的符號引用,而且將變量訪問轉化爲訪問這些變量的存儲結構所在的運行時內存位置的正確偏移 量。
  因爲動態連接的存在,經過晚期綁定(Late Binding)使用的其餘類的方法和變量在發生 變化時,將不會對調用它們的方法構成影響。網絡

分析Class文件的字節碼

  假設有這樣一個JAVA文件:併發

public class HelloWorld {
    String str = "";

    public String getStr()
    {
        return str;
    }

    public void setStr(String str)
    {
        this.str = str;
    }

}複製代碼

  編譯.Java文件:
javac -g HelloWorld.java
  輸出字節碼:
javap -verbose HelloWorld
  顯示以下的內容:函數

public class test01.HelloWorld
  SourceFile: "HelloWorld.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#21         //  java/lang/Object."<init>":()V
   #2 = String             #22            //
   #3 = Fieldref           #4.#23         //  test01/HelloWorld.str:Ljava/lang/String;
   #4 = Class              #24            //  test01/HelloWorld
   #5 = Class              #25            //  java/lang/Object
   #6 = Utf8               str
   #7 = Utf8               Ljava/lang/String;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               Ltest01/HelloWorld;
  #15 = Utf8               getStr
  #16 = Utf8               ()Ljava/lang/String;
  #17 = Utf8               setStr
  #18 = Utf8               (Ljava/lang/String;)V
  #19 = Utf8               SourceFile
  #20 = Utf8               HelloWorld.java
  #21 = NameAndType        #8:#9          //  "<init>":()V
  #22 = Utf8
  #23 = NameAndType        #6:#7          //  str:Ljava/lang/String;
  #24 = Utf8               test01/HelloWorld
  #25 = Utf8               java/lang/Object
{
  java.lang.String str;
    flags:

  public test01.HelloWorld();
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String
         7: putfield      #3                  // Field str:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 5: 0
        line 7: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      11     0  this   Ltest01/HelloWorld;

  public java.lang.String getStr();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #3                  // Field str:Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   Ltest01/HelloWorld;

  public void setStr(java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #3                  // Field str:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 16: 0
        line 17: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       6     0  this   Ltest01/HelloWorld;
               0       6     1   str   Ljava/lang/String;
}複製代碼

  說明:
  javac -g TestClass.java工具

  • -g:生成全部的調試信息,包括局部變量名和行號信息。
    javap -c TestClass > TCC.txt,對於javap經常使用的參數:
  • -c:輸出字節碼Code
  • -l(小寫L):輸出Code、LineNumberTable與LocalVariableTable
  • -s:輸出方法簽名(方法的接收參數列表和返回值)
  • -verbose:包含-c、-l以及輸出class文件的編譯版本,常量池,Stack, Locals, Args_size
  • 對於javap而言,經常使用的就是-c或-verbose

  使用16進制的Hex Fiend 查看字節文件獲得以下信息:ui

CA FE BA BE 00 00 00 33 00 1A 0A 00 05 00 15 08 00 16 09 00 04 00 17 07 00 18 07 00 19 01 00 03 73 74 72 01 00 12 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65 01 00 04 74 68 69 73 01 00 13 4C 74 65 73 74 30 31 2F 48 65 6C 6C 6F 57 6F 72 6C 64 3B 01 00 06 67 65 74 53 74 72 01 00 14 28 29 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 01 00 06 73 65 74 53 74 72 01 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0F 48 65 6C 6C 6F 57 6F 72 6C 64 2E 6A 61 76 61 0C 00 08 00 09 01 00 00 0C 00 06 00 07 01 00 11 74 65 73 74 30 31 2F 48 65 6C 6C 6F 57 6F 72 6C 64 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 04 00 05 00 00 00 01 00 00 00 06 00 07 00 00 00 03 00 01 00 08 00 09 00 01 00 0A 00 00 00 39 00 02 00 01 00 00 00 0B 2A B7 00 01 2A 12 02 B5 00 03 B1 00 00 00 02 00 0B 00 00 00 0A 00 02 00 00 00 05 00 04 00 07 00 0C 00 00 00 0C 00 01 00 00 00 0B 00 0D 00 0E 00 00 00 01 00 0F 00 10 00 01 00 0A 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 03 B0 00 00 00 02 00 0B 00 00 00 06 00 01 00 00 00 0B 00 0C 00 00 00 0C 00 01 00 00 00 05 00 0D 00 0E 00 00 00 01 00 11 00 12 00 01 00 0A 00 00 00 3E 00 02 00 02 00 00 00 06 2A 2B B5 00 03 B1 00 00 00 02 00 0B 00 00 00 0A 00 02 00 00 00 10 00 05 00 11 00 0C 00 00 00 16 00 02 00 00 00 06 00 0D 00 0E 00 00 00 00 00 06 00 06 00 07 00 01 00 01 00 13 00 00 00 02 00 14複製代碼

  先來看看Class的文件結構:

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]; 
}複製代碼

  其中u一、u二、u4分別表明一、二、4個字節無符號數。

magic:

   魔數,魔數的惟一做用是肯定這個文件是否爲一個能被虛擬機所接受的Class文件。魔數值固定爲0xCAFEBABE,不會改變。

## minor_version、major_version:
  分別爲Class文件的副版本和主版本。它們共同構成了Class文件的格式版本號。不一樣版本的虛擬機實現支持的Class文件版本號也相應不一樣,高版本號的虛擬機能夠支持低版本的Class文件,反之則不成立。

constant_pool_count:

    常量池計數器,constant_pool_count的值等於constant_pool表中的成員數加1。

## constant_pool[]:
   常量池,constant_pool是一種表結構,它包含Class文件結構及其子結構中引用的全部字符串常量、類或接口名、字段名和其它常量。常量池不一樣於其餘,索引從1開始到constant_pool_count -1。

## access_flags:
   訪問標誌,access_flags是一種掩碼標誌,用於表示某個類或者接口的訪問權限及基礎屬性。access_flags的取值範圍和相應含義見下表:

### this_class:
  類索引,this_class的值必須是對constant_pool表中項目的一個有效索引值。constant_pool表在這個索引處的項必須爲CONSTANT_Class_info類型常量,表示這個Class文件所定義的類或接口。

## super_class:
  父類索引,對於類來講,super_class的值必須爲0或者是對constant_pool表中項目的一個有效索引值。若是它的值不爲0,那constant_pool表在這個索引處的項必須爲CONSTANT_Class_info類型常量,表示這個Class文件所定義的類的直接父類。固然,若是某個類super_class的值是0,那麼它一定是java.lang.Object類,由於只有它是沒有父類的。

interfaces_count:

   接口計數器,interfaces_count的值表示當前類或接口的直接父接口數量。

interfaces[]:

  接口表,interfaces[]數組中的每一個成員的值必須是一個對constant_pool表中項目的一個有效索引值,它的長度爲interfaces_count。每一個成員interfaces[i] 必須爲CONSTANT_Class_info類型常量。

## fields_count:
  字段計數器,fields_count的值表示當前Class文件fields[]數組的成員個數。

## fields[]:
  字段表,fields[]數組中的每一個成員都必須是一個fields_info結構的數據項,用於表示當前類或接口中某個字段的完整描述。

## methods_count:
  方法計數器,methods_count的值表示當前Class文件methods[]數組的成員個數。

## methods[]:
  方法表,methods[]數組中的每一個成員都必須是一個method_info結構的數據項,用於表示當前類或接口中某個方法的完整描述。

## attributes_count:
  屬性計數器,attributes_count的值表示當前Class文件attributes表的成員個數。

## attributes[]:
  屬性表,attributes表的每一個項的值必須是attribute_info結構。
  從新看一下Class File的表結構:

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]; 
}複製代碼


  能夠看到前4個字節爲魔數,也就是0xCAFEBABE,這裏都是十六進制。接下來兩個字節是副版本號,是00。

  再接下來是副版本號:

  0x0033 轉換成十進制是51。
  接下來是常量池個數。

  0x001A 轉換成10進制是26。但按理說應該是從索引0到25,但實際上只有1到25。
  接下來的數據就是常量池的數據。
  全部常量池的數據都有以下的通用結構:

cp_info 
{ 
  u1 tag; 
  u1 info[]; 
}複製代碼

  以1個字節的tag開頭,後面info[]項的內容tag由的類型所決定。tag有效的類型和對應的取值以下表:
  


  下面咱們來介紹下不一樣類型的tag所對應的結構和規則:
  CONSTANT_Class_info結構:
  CONSTANT_Class_info結構用於表示類或接口,格式以下:

CONSTANT_Class_info 
{ 
    u1 tag; 
    u2 name_index; 
}複製代碼

  CONSTANT_Class_info結構的tag項的值爲CONSTANT_Class(7)。name_index項的值,必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表明一個有效的類或接口二進制名稱的內部形式。
  CONSTANT_Fieldref_info, CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info結構:
  字段,方法和接口方法由相似的結構表示:

CONSTANT_Fieldref_info 
{ 
    u1 tag; 
    u2 class_index; 
    u2 name_and_type_index; 
}
CONSTANT_Methodref_info 
{ 
    u1 tag; 
    u2 class_index; 
    u2 name_and_type_index; 
}
CONSTANT_InterfaceMethodref_info 
{ 
    u1 tag; 
    u2 class_index; 
    u2 name_and_type_index; 
}複製代碼

  CONSTANT_Fieldref_info結構的tag項的值爲CONSTANT_Fieldref(9)
  CONSTANT_Methodref_info結構的tag項的值爲CONSTANT_Methodref(10)。 CONSTANT_InterfaceMethodref_info結構的tag項的值爲CONSTANT_InterfaceMethodref(11)
  class_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Class_info結構,表示一個類或接口,當前字段或方法是這個類或接口的成員。
  name_and_type_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,它表示當前字段或方法的名字和描述符。
  CONSTANT_String_info結構:
  CONSTANT_String_info用於表示java.lang.String類型的常量對象,格式以下:

CONSTANT_String_info 
{
    u1 tag; 
    u2 string_index; 
}複製代碼

  CONSTANT_String_info結構的tag項的值爲CONSTANT_String(8)。string_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示一組Unicode碼點序列,這組Unicode碼點序列最終會被初始化爲一個String對象。

CONSTANT_Integer_info和CONSTANT_Float_info結構:

  CONSTANT_Intrger_info和CONSTANT_Float_info結構表示4字節(int和float)的數值常量: 

CONSTANT_Integer_info 
{ 
    u1 tag; 
    u4 bytes; 
} 
CONSTANT_Float_info 
{ 
    u1 tag; 
    u4 bytes; 
}複製代碼

  CONSTANT_Integer_info結構的bytes項表示int常量的值,按照Big-Endian的順序存儲。 CONSTANT_Float_info結構的bytes項按照IEEE 754單精度浮點格式。表示float常量的值,按照Big-Endian的順序存儲。
  CONSTANT_Long_info和CONSTANT_Double_info結構:
  CONSTANT_Long_info和CONSTANT_Double_info結構表示8字節(long和double)的數值常量:

CONSTANT_Long_info 
{ 
    u1 tag; 
    u4 high_bytes; 
    u4 low_bytes; 
} 
CONSTANT_Double_info 
{ 
    u1 tag; 
    u4 high_bytes; 
    u4 low_bytes; 
}複製代碼

  在Class文件的常量池中,全部的8字節的常量都佔兩個表成員(項)的空間。若是一個CONSTANT_Long_info或CONSTANT_Double_info結構的項在常量池中的索引爲n,則常量池中下一個有效的項的索引爲n+2,此時常量池中索引爲n+1的項有效但必須被認爲不可用。
  CONSTANT_Long_info結構的tag項的值是CONSTANT_Long(5)。 CONSTANT_Double_info結構的tag項的值是CONSTANT_Double(6)。
  CONSTANT_Long_info結構中的無符號的high_bytes和low_bytes項用於共同表示long型常量,構造形式爲((long) high_bytes << 32) + low_bytes,high_bytes和low_bytes都按照Big-Endian順序存儲。 CONSTANT_Double_info結構中的high_bytes和low_bytes共同按照IEEE 754雙精度浮點格式表示double常量的值。high_bytes和low_bytes都按照Big-Endian順序存儲。
  CONSTANT_NameAndType_info結構:
  CONSTANT_NameAndType_info結構用於表示字段或方法,可是和前面介紹的三個表示字段方法的結構不一樣,CONSTANT_NameAndType_info結構沒有標識出它所屬的類或接口,格式以下:

CONSTANT_NameAndType_info 
{ 
    u1 tag; 
    u2 name_index; 
    u2 descriptor_index; 
}複製代碼

  CONSTANT_NameAndType_info結構的tag項的值爲CONSTANT_NameAndType(12)。

  name_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,這個結構要麼表示特殊的方法名 ,要麼表示一個有效的字段或方法的非限定名。
  descriptor_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info(§4.4.7)結構,這個結構表示一個有效的字段描述符或方法描述符。
   CONSTANT_Utf8_info結構:
   CONSTANT_Utf8_info結構用於表示字符串常量的值:

CONSTANT_Utf8_info 
{ 
    u1 tag; 
    u2 length; 
    u1 bytes[length]; 
}複製代碼

  CONSTANT_Utf8_info結構的tag項的值爲CONSTANT_Utf8(1)。length項的值指明瞭bytes[]數組的長度,bytes[]是表示字符串值的byte數組。
  CONSTANT_MethodHandle_info結構:
  CONSTANT_MethodHandle_info結構用於表示方法句柄,結構以下:

CONSTANT_MethodHandle_info 
{
    u1 tag; 
    u1 reference_kind; 
    u2 reference_index; 
}複製代碼

  CONSTANT_MethodHandle_info結構的tag項的值爲CONSTANT_MethodHandle(15)reference_kind項的值必須在1至9之間(包括1和9),它決定了方法句柄的類型。
  reference_index項的值必須是對常量池的有效索引,索引項和reference_kind的對應關係以下:
  CONSTANT_MethodType_info結構:
 CONSTANT_MethodType_info結構用於表示方法類型:

CONSTANT_MethodType_info 
{ 
    u1 tag; 
    u2 descriptor_index; 
}複製代碼

  CONSTANT_MethodType_info結構的tag項的值爲CONSTANT_MethodType(16)。descriptor_index項的值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示方法的描述符。
  CONSTANT_InvokeDynamic_info結構:
  CONSTANT_InvokeDynamic_info用於表示invokedynamic指令所使用到的引導方法、引導方法使用到動態調用名稱、參數和請求返回類型以及能夠選擇性的附加被稱爲靜態參數的常量序列。

CONSTANT_InvokeDynamic_info 
{ 
    u1 tag; 
    u2 bootstrap_method_attr_index; 
    u2 name_and_type_index; 
}複製代碼

  CONSTANT_InvokeDynamic_info結構的tag項的值爲CONSTANT_InvokeDynamic(18)。bootstrap_method_attr_index項的值必須是對當前Class文件中引導方法表的bootstrap_methods[]數組的有效索引。ame_and_type_index項的值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,表示方法名和方法描述符

#1 = Methodref          #5.#21         //  java/lang/Object."<init>":()V複製代碼

Methodref 在常量類型表中間是10。觀察字節碼:


0A 恰好是10。那麼接下來就是methodref的結構:

CONSTANT_Methodref_info 
{ 
    u1 tag; 
    u2 class_index; 
    u2 name_and_type_index; 
}複製代碼

class_index 爲0x0005,name_and_type_index爲0015。正好對應「#5.#21」

接下來就是按照這種模式分析就能夠了。

注意#25 = Utf8 java/lang/Object 這就說明索引25存儲的是java/lang/Object這個UTF-8字符串。UTF-8的字符串以下:
6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74


常量池完畢之後,就是JAVA類自己的信息了(javap 出現的信息也能夠看出來)

類自己的信息

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]; 
}複製代碼

常量池後面兩個字節是訪問標誌access_flags:00 21
其中ACC_PUBLIC的值爲0x0001,ACC_SUPER的值爲0x0020,這個字段是複合的。

字段

  每一個字段(Field)都由field_info結構所定義,在同一個Class文件中,不會有兩個字段同時具備相同的字段名和描述符。
  field_info結構格式以下: 

field_info
{ 
    u2 access_flags; 
    u2 name_index; 
    u2 descriptor_index; 
    u2 attributes_count; 
    attribute_info attributes[attributes_count]; 
}複製代碼

  access_flags項的值是用於定義字段被訪問權限和基礎屬性的掩碼標誌。取值範圍以下表:
  name_index項的值必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示一個有效的字段的非全限定名。
  descriptor_index項的值必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示一個有效的字段的描述符。
  attributes_count的項的值表示當前字段的附加屬性的數量。
  attributes表的每個成員的值必須是attribute結構,一個字段能夠有任意個關聯屬性。

方法

  全部方法(Method),包括實例初始化方法和類初始化方法在內,都由method_info結構所定義。在一個Class文件中,不會有兩個方法同時具備相同的方法名和描述符。method_info結構格式以下:

method_info 
{ 
    u2 access_flags; 
    u2 name_index; 
    u2 descriptor_index; 
    u2 attributes_count; 
    attribute_info attributes[attributes_count]; 
}複製代碼

  access_flags項的值是用於定義當前方法的訪問權限和基本屬性的掩碼標誌,取值範圍以下表:
  
  標記名        值        說明

  ACC_PUBLIC     0x0001     public,方法能夠從包外訪問

  ACC_PRIVATE    0x0002     private,方法只能本類中訪問

  ACC_PROTECTED  0x0004     protected,方法在自身和子類能夠訪問

  ACC_STATIC     0x0008     static,靜態方法

  ACC_FINAL      0x0010     final,方法不能被重寫(覆蓋)

  ACC_SYNCHRONIZED     0x0020     synchronized,方法由管程同步

  ACC_BRIDGE     0x0040     bridge,方法由編譯器產生

  ACC_VARARGS     0x0080     表示方法帶有變長參數

  ACC_NATIVE     0x0100     native,方法引用非java語言的本地方法

  ACC_ABSTRACT   0x0400     abstract,方法沒有具體實現

  ACC_STRICT     0x0800     strictfp,方法使用FP-strict浮點格式

  ACC_SYNTHETIC   0x1000     方法在源文件中不出現,由編譯器產生

  name_index項的值必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構。
  descriptor_index項的值必須是對常量池的一個有效索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示一個有效的方法的描述符。
  attributes_count的項的值表示這個方法的附加屬性的數量。attributes表的每個成員的值必須是attribute結構,一個方法能夠有任意個與之相關的屬性。

屬性:

  屬性(Attributes)在Class文件格式中的ClassFile結構、field_info 結構,method_info結構和Code_attribute結構都有使用,全部屬性的通用格式以下:

attribute_info 
{ 
    u2 attribute_name_index; 
    u4 attribute_length; 
    u1 info[attribute_length]; 
}複製代碼

  對於任意屬性,attribute_name_index必須是對當前Class文件的常量池的有效16位無符號索引。常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示當前屬性的名字。attribute_length項的值給出了跟隨其後的字節的長度,這個長度不包括attribute_name_index和attribute_name_index項的6個字節。
  對於字段、方法、和屬性的結構,咱們很容易的能夠經過javap工具查看到。
Class的文件結構是十分複雜的,要在一篇博客裏面講清楚是不太可能的,只能算是入門。有什麼問題的話能夠在評論裏提問。

 

小廣告

新書《Java併發編程系統與模型》目前已經寫完一部分。可是實際上說實話,並不知道讀者們對java併發系統有哪些比較關心的,並且閉門造車實在是太累,寫出來怕沒人看。因此我放在這裏徵求你們的意見。你們能夠直接在評論裏提問或者但願做者重點描寫哪一部份內容,我好有針對性的寫。

相關文章
相關標籤/搜索