分析一個Java Class文件

fpcm 9年前發布 | 20K 次閱讀 Java Java開發

Java源碼文件TestClass.java:

package jvm.chapter6;
//P166
public class TestClass {
  private int m;
  public int inc(){
    return m+1;
  }
}

展示這個Class文件的16進制內容:

分析一個Java Class文件

從頭開始分析=>

ca fe ba be :magic number;

00 00 00 34 : 版本號是1.8.0;

00 16: 說明常量池有21個常量,1-21, index留做他用;接下來就是分別這21個常量的描述:

07/00 02 :CONSTANT_Class_info 常量,類名索引是該常量池的第2項;

01/00 16/6a 76 6d 2f 63 68 61 70  74 65 72 36 2f 54 65 73 74 43 6c 61 73 73 :這是該常量池的第2項,CONSTANT_Utf8_info常量,長度是22(0x0016),正是字符串常量“jvm/chapter6 /TestClass”;

07/00  04:CONSTANT_Class_info 常量,類名索引是該常量池的第4項;

01/00 10/6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74:這是該常量池的第4項,CONSTANT_Utf8_info常量,長度是16(0x0010),正是字符串常量“java/lang /Object”;

01/00 01/6d :CONSTANT_Utf8_info常量,是字符串常量"m";

01/00 01/49 :CONSTANT_Utf8_info常量,是字符串常量"m";

01/00 06/3c  69 6e 69 74 3e :CONSTANT_Utf8_info常量,是字符串常量"<init>";

01/00 03/28 29 56 :CONSTANT_Utf8_info常量,是字符串常量"()V";

01/00 04/43 6f  64 65 :CONSTANT_Utf8_info常量,是字符串常量"Code";

0a/00 03/00 0b: tag=10,表示CONSTANT_Methodref_info常量,聲明該方法的類名稱位于常量池的第3項,即java/lang/Object,方法名和類型NameAndType="<init>":()V;

0c/00 07/00 08: 接下來tag=12就是上面剛說道的CONSTANT_NameAndType_info類型,接下來的兩個索引分別指向方法名和描述符即"<init>"和“()V”,

01/00 0f/4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 :CONSTANT_Utf8_info常量,是字符串常量"LineNumberTable";

01/00 12/4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61  62 6c 65 :CONSTANT_Utf8_info常量,是字符串常量"LocalVariableTable";

01/00 04/74 68  69 73 :CONSTANT_Utf8_info常量,是字符串常量"this";

01/00 18/4c 6a 76 6d 2f 63 68 61 70 74 65 72 36 2f 54 65 73 74 43 6c 61 73 73 3b:CONSTANT_Utf8_info常量,是字符串常量"Ljvm/chapter6/TestClass;";

01/00 03 69 6e 63 :CONSTANT_Utf8_info常量,是字符串常量"inc";

01/00 03 28 29 49 :CONSTANT_Utf8_info常量,是字符串常量"()I";

09/00 01/00 13:代表Fieldref常量,然后接下來兩個u2分別表示聲明該字段的類和NameAndType的索引;

0c/00 05/00 06:就是上面剛使用的NameAndType常量;

01/00 0a/53 6f 75 72 63 65 46 69 6c 65 :CONSTANT_Utf8_info常量,是字符串常量"SourceFile";

01 00 0e 54 65 73 74 43  6c 61 73 73 2e 6a 61 76 61::CONSTANT_Utf8_info常量,是字符串常量"TestClass.java";

上面就分析完了常量池中的字段,

00 21 : access_flags= ACC_PUBLIC | ACC_SUPER = 0x0021;

00 01 00 03 00  00:類索引 父類索引 和接口索引,常量池第3項表示的是Object,說明了默認的父類就是Object,沒有實現接口,所以interfaces_count=0;

00 01/00 02/00 05/00 06/00 00: 接下來的是字段表集合(fields_info),fields_count=1,access_flags=0x0002=ACC_PRIVATE, 代表字段名稱的name_index=5(常量池的第5項是CONSTANT_Utf8_info類型的字符串m),字段描述符 descriptor_index=0x0006(常量池的第5項是CONSTANT_Utf8_info類型的字符串I),最后 atributes_count=0,說明不包含額外的信息。總上這些信息正是為了說明定義的字段“private int m”;

00 02/00 01/00  07/00 08/00 01/00 09/00 00 00 2f/00 01/00 01/00  00 00 05/2a b7 00 0a b1:接下來的是方法集合(methods_info),有兩個方法,第一個方法的訪問標志是0x0001=ACC_PUBLIC,名稱索引是 0x0007,指向常量池中的<init>,描述符索引值是0x0008,指向常量池中的"()V",接下來屬性表計數器 attributes_count=1,屬性名稱索引是0x0009對應的是“Code”,接下來就是Code屬性表的結構:屬性值的長度 attribute_length=0x2f;操作數棧的最大深度和本地變量表的容量都是0x0001;字節碼指令長度是5,翻譯“2a b7 00 0a b1”的過程是:

1)讀入2a,對應的是aload_0 ,將第一個引用類型的本地變量推送到操作數棧頂;

2)讀入b7,對應的是invokespecial,作用是調用以棧頂的reference類型所指向的對象的構造器方法 實例初始化方法 私有方法,接下來有一個u2類型的參數說明具體調用哪個方法,它指向常量池中一個CONSTANT_Methodref_info的常量,即那個方法的符 號引用;

3)00 0a正是上面invokespecial的參數,指向常量池中的一個Methodref常量,對應的應該是Object.init()方法;

4)b1,對應的指令是return,并且返回類型是oid,當前方法對應的指令結束。

繼續上面的分析,

00 00/00 02/00 0c/00 00 00 06/00 01/00 00/00 03|00 0d/00 00 00 0c/00 01|00 00/00 05/00 0e/00 0f/00 00:exception_table_length=0,該方法沒有拋出異常,然后attributes_count=2,該Code屬性有倆屬 性:0x000c指向常量池中的 “LineNumberTable”,attribute_length=6,line_number_table_length=1,就是記錄了一對 java源碼行號和字節碼行號的對應關系(3:0);0x000d指向常量池中的 “LocalVariableTable”,attribute_length=12,local_variable_table_length=1(代 表了一個棧幀和程序中的局部變量關聯關系),start_pc=0x0000,length=0x0005表征該局部變量在字節碼中的作用范圍(就是這個 方法內),name_index=0x000e, descriptor_index=0x000f表示該局部變量的名稱及其描述符,即為“this”, "Ljvm/chapter6/TestClass";通過javap的輸出都可以一目了然。index=0x0000代表該局部變量在棧幀局部變量表中 Slot的位置。

接下來看第二個方法,

00 01/00 10/00 11/00 01/00 09/00 00 00 31/00 02/00 01/00 00 00 07/2a b4 00 12 04 60 ac/:方法的訪問標志是0x0001=ACC_PUBLIC,名稱索引是16,指向常量池中的“inc”,描述符索引值是0x0011,指向常量池中 的"()I",接下來屬性表計數器attributes_count=1,屬性名稱索引是0x0009對應的是“Code”,接下來就是Code屬性表的 結構:屬性值的長度attribute_length=0x31;操作數棧的最大深度是2,本地變量表的容量是1;字節碼指令長度是7,翻譯“2a b4 00 12 04 60 ac”的過程是:

1)讀入2a,對應的是aload_0 ,將第一個引用類型的本地變量推送到操作數棧頂;

2)讀入b4,對應的指令是getfield,獲得實例域,并壓入棧頂,他需要參數用來說明把哪個變量入棧,即下面;

3)讀入00 12,用來指明getfield 哪個字段,指向常量池中的一個Fieldref常量,“jvm/chapter6/TestClass.m:I”;

4)讀入04,對應的指令是iconst_1,將int類型的1推至棧頂;

5)讀入60,對應的指令是iadd,將棧頂兩個int類型的數值相加并把結果壓入棧頂;

6)讀入ac,對應的指令是ireturn,從當前方法返回int,結束。

后面的倆屬性就和分析第一個方法類似。

至此,就分析完了一個簡單的Class文件的結構,清晰了很多。

附:

Javap TestClass.class的結果:$ javap -verbose TestClass.class

Classfile /home/vonzhou/GitHub/JavaProject/learning-java/bin/jvm/chapter6/TestClass.class

Last modified Apr 29, 2015; size 379 bytes

MD5 checksum 70a67e773b621619d03f9d1b5ac91af6

Compiled from "TestClass.java"

public class jvm.chapter6.TestClass

SourceFile: "TestClass.java"

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Class              #2;            //  jvm/chapter6/TestClass

#2 = Utf8               jvm/chapter6/TestClass;

#3 = Class              #4;            //  java/lang/Object

#4 = Utf8               java/lang/Object;

#5 = Utf8               m;

#6 = Utf8               I;

#7 = Utf8               <init>;

#8 = Utf8               ()V;

#9 = Utf8               Code;

#10 = Methodref          #3.#11;        //  java/lang/Object."<init>":()V

#11 = NameAndType        #7:#8;         //  "<init>":()V

#12 = Utf8               LineNumberTable;

#13 = Utf8               LocalVariableTable;

#14 = Utf8               this;

#15 = Utf8               Ljvm/chapter6/TestClass;;

#16 = Utf8               inc;

#17 = Utf8               ()I;

#18 = Fieldref           #1.#19;        //  jvm/chapter6/TestClass.m:I

#19 = NameAndType        #5:#6;         //  m:I

#20 = Utf8               SourceFile;

#21 = Utf8               TestClass.java;

{

public jvm.chapter6.TestClass();

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0       

1: invokespecial #10;                // Method java/lang/Object."<init>":()V

4: return        

LineNumberTable:

line 3: 0

LocalVariableTable:

Start  Length  Slot  Name   Signature

0       5       0    this   Ljvm/chapter6/TestClass;

public int inc();

flags: ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: aload_0       

1: getfield      #18;                // Field m:I

4: iconst_1      

5: iadd          

6: ireturn       

LineNumberTable:

line 6: 0

LocalVariableTable:

Start  Length  Slot  Name   Signature

0       7      0     this   Ljvm/chapter6/TestClass;

}

作者:vonzhoufz 發表于2015/5/1 9:34:11 原文鏈接

 本文由用戶 fpcm 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!