Java class文件探索

本文將通過對一個簡單的Java源文件可训,編譯成的class文件進(jìn)行分析绘梦,以探索Java class文件的格式嘀掸。

package ex3;

public class ConstantTest {
    int a;
    public void inc() {
        ++a;
    }
}

將這個Java代碼保存為ConstantTest.java,使用javac ConstantTest.java实幕,將其編譯成ConstantTest.class字節(jié)碼文件吝镣。使用十六進(jìn)制查看器hexdump,查看其內(nèi)容hexdump -C ConstantTest.class±ケ樱現(xiàn)在看不懂沒關(guān)系末贾,下面我們逐字節(jié)分析。

00000000  ca fe ba be 00 00 00 34  00 12 0a 00 04 00 0e 09  |.......4........|
00000010  00 03 00 0f 07 00 10 07  00 11 01 00 01 61 01 00  |.............a..|
00000020  01 49 01 00 06 3c 69 6e  69 74 3e 01 00 03 28 29  |.I...<init>...()|
00000030  56 01 00 04 43 6f 64 65  01 00 0f 4c 69 6e 65 4e  |V...Code...LineN|
00000040  75 6d 62 65 72 54 61 62  6c 65 01 00 03 69 6e 63  |umberTable...inc|
00000050  01 00 0a 53 6f 75 72 63  65 46 69 6c 65 01 00 11  |...SourceFile...|
00000060  43 6f 6e 73 74 61 6e 74  54 65 73 74 2e 6a 61 76  |ConstantTest.jav|
00000070  61 0c 00 07 00 08 0c 00  05 00 06 01 00 10 65 78  |a.............ex|
00000080  33 2f 43 6f 6e 73 74 61  6e 74 54 65 73 74 01 00  |3/ConstantTest..|
00000090  10 6a 61 76 61 2f 6c 61  6e 67 2f 4f 62 6a 65 63  |.java/lang/Objec|
000000a0  74 00 21 00 03 00 04 00  00 00 01 00 00 00 05 00  |t.!.............|
000000b0  06 00 00 00 02 00 01 00  07 00 08 00 01 00 09 00  |................|
000000c0  00 00 1d 00 01 00 01 00  00 00 05 2a b7 00 01 b1  |...........*....|
000000d0  00 00 00 01 00 0a 00 00  00 06 00 01 00 00 00 03  |................|
000000e0  00 01 00 0b 00 08 00 01  00 09 00 00 00 27 00 03  |.............'..|
000000f0  00 01 00 00 00 0b 2a 59  b4 00 02 04 60 b5 00 02  |......*Y....`...|
00000100  b1 00 00 00 01 00 0a 00  00 00 0a 00 02 00 00 00  |................|
00000110  06 00 0a 00 07 00 01 00  0c 00 00 00 02 00 0d     |...............|
0000011f

先看看class文件的反匯編內(nèi)容javap -v ContantTest.class整吆。

public class ex3.ConstantTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#14         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#15         // ex3/ConstantTest.a:I
   #3 = Class              #16            // ex3/ConstantTest
   #4 = Class              #17            // java/lang/Object
   #5 = Utf8               a
   #6 = Utf8               I
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               inc
  #12 = Utf8               SourceFile
  #13 = Utf8               ConstantTest.java
  #14 = NameAndType        #7:#8          // "<init>":()V
  #15 = NameAndType        #5:#6          // a:I
  #16 = Utf8               ex3/ConstantTest
  #17 = Utf8               java/lang/Object
{
  int a;
    descriptor: I
    flags:

  public ex3.ConstantTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public void inc();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #2                  // Field a:I
         5: iconst_1
         6: iadd
         7: putfield      #2                  // Field a:I
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 10
}

class文件結(jié)構(gòu)

Class文件是一組以8bit字節(jié)為基礎(chǔ)單位的二進(jìn)制流拱撵, 各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊地排列在文件之中,中間沒有添加任何分隔符掂为,這使得整個Class文件中存儲的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù)裕膀,沒有空隙存在员串。 當(dāng)遇到需要占用8bit以上空間的數(shù)據(jù)項時勇哗,則會按照高位在前(大端序)的方式分割成若干個字節(jié)進(jìn)行存儲。

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];
}

Class文件格式可以用上面這個結(jié)構(gòu)體來表示寸齐,其中的u2/u4表示2字節(jié)和4字節(jié)的數(shù)據(jù)欲诺。我們將其分為以下幾個部分來探討。

  • magic
  • versioin
  • 常量池
  • access_flags
  • 類索引渺鹦、父類索引與接口索引集合
  • 字段表
  • 方法表
  • 屬性表

magic

magic是一個四字節(jié)的數(shù)據(jù)扰法,它唯一的功能是確定這個文件是否為一個能被虛擬機(jī)接受的Class文件,它的值是固定的0xCAFEBABE毅厚,是Cafe Babe的意思塞颁,這是Java在設(shè)計之初就已經(jīng)決定了的。

使用hexdump -C XXX.class | head -n 2 可以查看class文件的十六進(jìn)制表示,可以看到它的前4字節(jié)是ca fe ba be祠锣。

00000000  ca fe ba be 00 00 00 34  00 12 0a 00 04 00 0e 09  |.......4........|

注意:大端序與我們的閱讀順序相同酷窥。例如0x12345678中12是高位字節(jié),78是低位字節(jié)伴网,而class文件中的第1個字節(jié)是低位地址蓬推,第4個字節(jié)是高位地址,大端序中高位字節(jié)放在低位地址澡腾,即12放在第1個字節(jié)沸伏,78放在第4個字節(jié)。如果是小端序动分,就要反過來毅糟,在文件中看到的就是78 56 45 12。

Linux中的file命令可以用來判斷文件類型澜公,它可以利用這個magic來判斷一個文件是不是class文件留特。

version

緊接著magic的4個字節(jié)存儲的是Class文件的版本號: 第5和第6個字節(jié)是minor_version , 第7和第8個字節(jié)是major_version玛瘸。

Java的版本號是從45開始的蜕青, JDK 1.1之后的每個JDK大版本發(fā)布主版本號向上加1( JDK 1.0~1.1使用了45.0~45.3的版本號),高版本的JDK能向下兼容以前版本的Class文件糊渊, 但不能運(yùn)行以后版本的Class文件右核, 因?yàn)椤?Java虛擬機(jī)規(guī)范》 在Class文件校驗(yàn)部分明確要求了即使文件格式并未發(fā)生任何變化, 虛擬機(jī)也必須拒絕執(zhí)行超過其版本號的Class文件渺绒。

在上面的例子中贺喝,minor_version為00 00,而major_version為00 34即52宗兼,因此該class文件版本號為52.0躏鱼,是由JDK 8編譯而來。

關(guān)于次版本號殷绍, 曾經(jīng)在現(xiàn)代Java( 即Java 2)出現(xiàn)前被短暫使用過染苛, JDK 1.0.2支持的版本45.0~45.3( 包括45.0~45.3),JDK 1.1支持版本45.0~45.65535主到, 從JDK 1.2以后茶行, 直到JDK 12之前次版本號均未使用, 全部固定為零登钥。 而到了JDK 12時期畔师, 由于JDK提供的功能集已經(jīng)非常龐大, 有一些復(fù)雜的新特性需要以“公測”的形式放出牧牢, 所以設(shè)計者重新啟用了副版本號看锉, 將它用于標(biāo)識“技術(shù)預(yù)覽版”功能特性的支持姿锭。 如果Class文件中使用了該版本JDK尚未列入正式特性清單中的預(yù)覽功能, 則必須把次版本號標(biāo)識為65535伯铣, 以便Java虛擬機(jī)在加載類文件時能夠區(qū)分出來艾凯。

常量池

constant_pool_count是一個u2類型的數(shù)據(jù),代表了常量池容量計數(shù)器懂傀, 這個容量計數(shù)是從1而不是0開始的趾诗。如上面例子中的00 12即十進(jìn)制18,那么常量池的索引范圍為1~17蹬蚁。在Class文件格式規(guī)范制定之時恃泪, 設(shè)計者將第0項常量空出來是有特殊考慮的, 這樣做的目的在于犀斋, 如果后面某些指向常量池的索引值的數(shù)據(jù)在特定情況下
需要表達(dá)“不引用任何一個常量池項目”的含義贝乎,可以把索引值設(shè)置為0來表示。

常量池中主要存放兩大類常量: 字面量( Literal) 和符號引用( Symbolic References) 叽粹。

  • 字面量比較接近于Java語言層面的常量概念览效, 如文本字符串、 被聲明為final的常量值等虫几。
  • 而符號引用則屬于編譯原理方面的概念锤灿, 主要包括下面幾類常量:
    • 被模塊導(dǎo)出或者開放的包( Package)
    • 類和接口的全限定名( Fully Qualified Name)
    • 字段的名稱和描述符( Descriptor)
    • 方法的名稱和描述符
    • 方法句柄和方法類型( Method Handle、 Method Type辆脸、 Invoke Dynamic)
    • 動態(tài)調(diào)用點(diǎn)和動態(tài)常量( Dynamically-Computed Call Site但校、 Dynamically-Computed Constant)

以下是JDK 8支持的常量類型。

常量類型 標(biāo)志 描述
CONSTANT_Class 7 類或接口的符號引用
CONSTANT_Fieldref 9 字段的符號引用
CONSTANT_Methodref 10 類中方法的符號引用
CONSTANT_InterfaceMethodref 11 接口中方法的符號引用
CONSTANT_String 8 字符串類型字面量
CONSTANT_Integer 3 整型字面量
CONSTANT_Float 4 浮點(diǎn)型字面量
CONSTANT_Long 5 長整形字面量
CONSTANT_Double 6 雙精度浮點(diǎn)型字面量
CONSTANT_NameAndType 12 字段或方法的部分符號引用
CONSTANT_Utf8 1 UTF8編碼的字符串
CONSTANT_MethodHandle 15 方法句柄
CONSTANT_MethodType 16 方法類型
CONSTANT_InvokeDynamic 18 動態(tài)計算常量

這些常量類型彼此都沒有什么關(guān)系啡氢,它們是不同的結(jié)構(gòu)體状囱,對應(yīng)著字節(jié)碼中一段字節(jié)流,這段字節(jié)流的長度因常量類型而異倘是。它們的共性是都由1字節(jié)的tag開頭亭枷。

00000000  ca fe ba be 00 00 00 34  00 12 0a 00 04 00 0e 09  |.......4........|
00000010  00 03 00 0f 07 00 10 07  00 11 01 00 01 61 01 00  |.............a..|
00000020  01 49 01 00 06 3c 69 6e  69 74 3e 01 00 03 28 29  |.I...<init>...()|

上面例子中常量池的第1項的tag為0a,是一個Methodref搀崭,它的結(jié)構(gòu)如下叨粘,一共占5字節(jié)0a 00 04 00 0e。

  • class_index表示常量池中一個Class類型的下標(biāo)门坷,其值為00 04即第4項宣鄙。
  • name_and_type_index表示常量池中一個NameAndType類型的下標(biāo)袍镀,其值為00 0e即第14項默蚌。
CONSTANT_Methodref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

緊接著第2項的tag為09,是一個Fieldref苇羡,它的結(jié)構(gòu)與Methodref一樣绸吸,占用5字節(jié)09 00 03 00 0f。

  • class_index值為00 03即第3項。
  • name_and_type_index值為00 0f即第15項锦茁。
CONSTANT_Fieldref_info {
    u1 tag;
    u2 class_index;
    u2 name_and_type_index;
}

第3項的tag為07攘轩,是一個Class,它的結(jié)構(gòu)如下码俩,一共占用3字節(jié)07 00 10度帮。

  • name_index表示常量池中一個Utf8類型的下標(biāo),其值為00 10即第16項稿存。
CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

第4項的tag為07笨篷,同上,指向第17項瓣履。

第5項的tag為01率翅,是一個Utf8,它的結(jié)構(gòu)如下袖迎,長度是變化的冕臭。

  • length是字符串的字節(jié)數(shù),其值為00 01即1燕锥。
  • bytes是一個字節(jié)數(shù)組辜贵,其值為0x61即97,是UTF-8編碼表示的字符串a(英文字符的UTF-8編碼與ASCII碼相同)归形∧罹保看到這里,應(yīng)該可以猜到连霉,這個是代碼中字段a的名稱了榴芳。
CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

第6項的tag01,同上跺撼,是一個字符串I窟感。

第7項的tag01,同上歉井,是一個字符串<init>柿祈。

第8項的tag01,同上哩至,是一個字符串()V躏嚎。

第9項的tag01,同上菩貌,是一個字符串Code卢佣。

第10項也是字符串LineNumberTable

第11項也是字符串inc箭阶。

第12項也是字符串SourceFile虚茶。

第13項也是字符串ConstantTest.java戈鲁。

00000070  61 0c 00 07 00 08 0c 00  05 00 06 01 00 10 65 78  |a.............ex|
00000080  33 2f 43 6f 6e 73 74 61  6e 74 54 65 73 74 01 00  |3/ConstantTest..|
00000090  10 6a 61 76 61 2f 6c 61  6e 67 2f 4f 62 6a 65 63  |.java/lang/Objec|
000000a0  74 00 21 00 03 00 04 00  00 00 01 00 00 00 05 00  |t.!.............|

第14項tag0c,是一個NameAndType嘹叫,其結(jié)構(gòu)如下婆殿,一共占用5字節(jié)0c 00 07 00 08。

  • name_index是常量池中一個Utf8_info的下標(biāo)罩扇,表示方法名稱婆芦,其值為00 07即7,即<init>喂饥。
  • descriptor_index也是一個Utf8_info的下標(biāo)寞缝,表示一個字段描述符(變量類型)或方法描述符(方法參數(shù)和返回值類型),其值為00 08即8仰泻,即()V表示方法沒有參數(shù)荆陆,返回void類型。

這一項合起來就是<init>:()V集侯,描述的是該類中的一個方法被啼,方法名稱為<init>,沒有參數(shù)棠枉,沒有返回值浓体。

CONSTANT_NameAndType_info {
    u1 tag;
    u2 name_index;
    u2 descriptor_index;
}

第15項tag0c,同上辈讶,合起來是a:I命浴,描述的是一個字段,字段名稱為a贱除,類型為int生闲。

第16項tag01,是一個Utf8月幌,長度為16碍讯,內(nèi)容為65 78 33 2f 43 6f 6e 73 74 61 6e 74 54 65 73 74,即ex3/ConstantTest扯躺。

第17項tag01捉兴,是一個Utf8,長度為16录语,內(nèi)容為6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74倍啥,即java/lang/Object

access_flags

訪問標(biāo)志 描述
ACC_PUBLIC 0x0001 public
ACC_FINAL 0x0010 final
ACC_SUPER 0x0020 JDK 1.0.2之后所有類都必須帶上這個標(biāo)志
ACC_INTERFACE 0x0200 interface
ACC_ABSTRACT 0x0400 abstract
ACC_SYNTHETIC 0x1000 標(biāo)識這個類不是由用戶代碼產(chǎn)生的
ACC_ANNOTATION 0x2000 annotation
ACC_ENUM 0x4000 enum

在常量池結(jié)束之后澎埠,緊接著的2個字節(jié)代表訪問標(biāo)志(access_flags)虽缕,這個標(biāo)志用于識別一些類或 者接口層次的訪問信息,包括:這個Class是類還是接口失暂;是否定義為public類型彼宠;是否定義為abstract 類型鳄虱;如果是類的話弟塞,是否被聲明為final凭峡;等等。

000000a0  74 00 21 00 03 00 04 00  00 00 01 00 00 00 05 00  |t.!.............|

上面例子中的access_flags值為00 21即决记,ACC_PUBLIC|ACC_SUPER摧冀,表示這是一個public類。

類索引系宫、父類索引與接口索引集合

類索引(this_class)和父類索引(super_class)都是一個u2類型的數(shù)據(jù)索昂,而接口索引集合 (interfaces)是一組u2類型的數(shù)據(jù)的集合,Class文件中由這三項數(shù)據(jù)來確定該類型的繼承關(guān)系扩借。類索 引用于確定這個類的全限定名椒惨,父類索引用于確定這個類的父類的全限定名。

由于Java語言不允許多 重繼承潮罪,所以父類索引只有一個康谆,除了java.lang.Object之外,所有的Java類都有父類嫉到,因此除了 java.lang.Object外沃暗,所有Java類的父類索引都不為0。

接口索引集合就用來描述這個類實(shí)現(xiàn)了哪些接口何恶,這些被實(shí)現(xiàn)的接口將按implements關(guān)鍵字(如果這個Class文件表示的是一個接口孽锥,則應(yīng)當(dāng)是extends關(guān)鍵字)后的接口順序從左到右排列在接口索引集合中。

他們都是u2類型的數(shù)據(jù)细层,是指向常量池中一個Class類型的索引惜辑,而Class類型的索引則包含指向一個Utf8的索引。

this_class是u2類型的數(shù)據(jù)疫赎,是指向常量池中一個Class類型的索引韵丑。上面例子中的值為00 03,即常量池中第3項虚缎,指向第16項撵彻,內(nèi)容為ex3/ConstantTest

super_class同上实牡,值為00 04陌僵,即第4項,指向第17項创坞,內(nèi)容為java/lang/Object碗短。

接口索引集合首先包含一個u2類型的數(shù)據(jù),表示接口數(shù)量题涨,然后向后尋找接口數(shù)量個指向Class類型的索引偎谁。例子中值為00 00总滩,表示沒有實(shí)現(xiàn)接口。

字段表

000000a0  74 00 21 00 03 00 04 00  00 00 01 00 00 00 05 00  |t.!.............|
000000b0  06 00 00 00 02 00 01 00  07 00 08 00 01 00 09 00  |................|

fields_count是u2數(shù)據(jù)巡雨,表示類擁有多少個字段闰渔。在上面例子中值為00 01,表明有1個字段铐望。

field_info結(jié)構(gòu)用于描述接口或者類中聲明的變量冈涧。Java語言中的“字段”(Field)包括類級變量以及實(shí)例級變量,但不包括在方法內(nèi)部聲明的局部變量正蛙。字段可以包括的修飾符有字段的作用域(public督弓、private、protected修飾 符)乒验、是實(shí)例變量還是類變量(static修飾符)愚隧、可變性(final)、并發(fā)可見性(volatile修飾符锻全,是否 強(qiáng)制從主內(nèi)存讀寫)狂塘、可否被序列化(transient修飾符)、字段數(shù)據(jù)類型(基本類型虱痕、對象睹耐、數(shù)組)、 字段名稱部翘。上述這些信息中硝训,各個修飾符都是布爾值,要么有某個修飾符新思,要么沒有窖梁,很適合使用標(biāo)志位來表示。而字段叫做什么名字夹囚、字段被定義為什么數(shù)據(jù)類型纵刘,這些都是無法固定的,只能引用常量池中的常量來描述荸哟。

field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

access_flags是方法的訪問修飾符假哎,與類的access_flags一樣,都是u2類型鞍历。在上面例子中值為00 00舵抹,表明沒有加修飾符。

標(biāo)志 描述
ACC_PUBLIC 0x0001 public
ACC_PRIVATE 0x0002 private
ACC_PROTECTED 0x0004 protected
ACC_STATIC 0x0008 static
ACC_FINAL 0x0010 final
ACC_VOLATILE 0x0040 volatile
ACC_TRANSIENT 0x0080 transient
ACC_SYNTHETIC 0x1000 是否由編譯器自動產(chǎn)生
ACC_ENUM 0x4000 enum

name_index是名稱索引劣砍,指向常量池中一個Utf8惧蛹,例子中值為00 05即第5項,是字符串a,表明這個字段名稱為a香嗓。

descriptor_index是描述符索引迅腔,指向常量池中一個Utf8,例子中值為00 06靠娱,是字符串I沧烈,表明這個字段類型為int

attributes_count是屬性數(shù)量饱岸,值為00 00掺出,表示沒有屬性徽千。

方法表

000000b0  06 00 00 00 02 00 01 00  07 00 08 00 01 00 09 00  |................|
000000c0  00 00 1d 00 01 00 01 00  00 00 05 2a b7 00 01 b1  |...........*....|

方法表結(jié)構(gòu)與字段表幾乎一樣苫费。

methods_count表示方法數(shù)量,在上面例子中值為00 02双抽,即有兩個方法百框,后面有兩個method_info項。

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

方法的access_flags與字段有所不同牍汹。

標(biāo)志 描述
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 由編譯器產(chǎn)生的橋接方法
ACC_VARARGS 0x0080 方法接受不定參數(shù)
ACC_NATIVE 0x0100 native
ACC_ABSTRACT 0x0400 abstract
ACC_STRICT 0x0800 strictfp
ACC_SYNTHETIC 0x1000 方法由編譯器生成

方法1

access_flags值為00 01铐维,表示是public方法。

name_index值為00 07慎菲,指向字符串<init>嫁蛇,這是方法名稱。

descriptor_index值為00 08露该,指向字符串()V睬棚,表示方法沒有參數(shù),返回類型為void解幼。

attributes_count值為00 01抑党,表示有1個attribute_info

attribute_info結(jié)構(gòu)如下撵摆,長度是變化的底靠。

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

attribute_name_index值為00 09,指向字符串Code特铝。

attribute_length值為00 00 00 1d即29暑中,表明后面的info字段長度為29字節(jié)。

000000c0  00 00 1d 00 01 00 01 00  00 00 05 2a b7 00 01 b1  |...........*....|
000000d0  00 00 00 01 00 0a 00 00  00 06 00 01 00 00 00 03  |................|

info是Code屬性鲫剿,其結(jié)構(gòu)定義如下鳄逾。

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];
}

max_stack值為00 01,表明操作數(shù)棧的深度為1牵素。

max_locals值為00 01严衬,表明局部變量數(shù)量為1。

code_length值為00 00 00 05笆呆,表明代碼長度為5请琳。

code值為2a b7 00 01 b1粱挡,代表的指令分別為

  • 2a aload_0
  • b7 00 01``invokespecial #00.#01`
  • b1 return

exception_table_length值為00 00,代表異常表為空俄精,即不拋出異常询筏。

attrbutes_count值為00 01,表明屬性表中有一個屬性竖慧。

attribute_name_index值為00 0a嫌套,指向字符串LineNumberTable。LineNumberTable屬性描述java源碼和字節(jié)碼的對應(yīng)關(guān)系圾旨,它并不是運(yùn)行時必備的屬性踱讨,但默認(rèn)會生成到class文件中】车模可以用javac -g:none來禁用LineNumberTable痹筛,這樣程序在拋出異常的時候?qū)⒉粫@示出錯的行號,并且在調(diào)試程序的時候廓鞠,也無法按照源碼行來設(shè)置斷點(diǎn)帚稠。

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_length值為00 00 00 06,表明后面還有6字節(jié)床佳。

line_number_table_length值為00 01滋早,表明line_number_table中有一項。

這一項的start_pc值為00 00砌们,line_number值為00 03杆麸,表明java源代碼中行號為3的位置對應(yīng)著class中該方法的起始位置。

方法2

000000e0  00 01 00 0b 00 08 00 01  00 09 00 00 00 27 00 03  |.............'..|
000000f0  00 01 00 00 00 0b 2a 59  b4 00 02 04 60 b5 00 02  |......*Y....`...|
00000100  b1 00 00 00 01 00 0a 00  00 00 0a 00 02 00 00 00  |................|
00000110  06 00 0a 00 07 00 01 00  0c 00 00 00 02 00 0d     |...............|
0000011f

access_flags為00 01怨绣,表明是public方法角溃。

name_index值為00 0b,指向字符串inc篮撑,這是方法名稱减细。

descriptor_index值為00 08,指向字符串()V赢笨,表示方法沒有參數(shù)未蝌,返回類型為void。

attributes_count值為00 01茧妒,表示有1個attribute_info萧吠。

attribute_name_index值為00 09,指向字符串Code桐筏,因此這個屬性也是Code屬性纸型。

attribute_length值為00 00 00 27即39,表明后面的info字段長度為39字節(jié)。

max_stack值為00 03狰腌,表明操作數(shù)棧的深度為3除破。

max_locals值為00 01,表明局部變量數(shù)量為1琼腔。

code_length值為00 00 00 0b瑰枫,表明代碼長度為11。

code值為2a 59 b4 00 02 04 60 b5 00 02 b1丹莲,代表的指令分別為光坝,顯然這與inc方法相符。

  • 2a aload_0
  • 59 dup
  • b4 00 02 getfield #00.#02
  • 02 iconst_1
  • 60 iadd
  • b5 00 02 putfield #00.#02
  • b1 return

exception_table_length值為00 00甥材,代表異常表為空盯另,即不拋出異常。

attrbutes_count值為00 01擂达,表明屬性表中有一個屬性土铺。

attribute_name_index值為00 0a胶滋,指向字符串LineNumberTable板鬓,表明是一個LineNumberTable屬性。

attribute_length值為00 00 00 0a究恤,表明后面還有10字節(jié)俭令。

line_number_table_length值為00 02,表明line_number_table中有2項部宿。

第1項的start_pc值為00 00抄腔,line_number值為00 06,表明java源代碼中行號為6的位置對應(yīng)著code的起始位置理张。

第2項的start_pc值為00 0a赫蛇,line_number值為00 07,表明java源代碼中行號為7的位置對應(yīng)著code第10字節(jié)的位置即return雾叭。

屬性表

00000110  06 00 0a 00 07 00 01 00  0c 00 00 00 02 00 0d     |...............|
0000011f

attributes_count值為00 01悟耘,表明有一個屬性。

attribute_name_index值為00 0c织狐,指向字符串SourceFile暂幼,表明是一個SourceFile屬性。其結(jié)構(gòu)如下移迫,占用8字節(jié)旺嬉。

SourceFile_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 sourcefile_index;
}

attribute_length值為00 00 00 02,表明attribute_length之后的屬性內(nèi)容一共占2字節(jié)厨埋。

sourcefile_index值為00 0d邪媳,指向常量池中第13向,即字符串ConstantTest.java

至此雨效,字節(jié)碼文件中的每一個字節(jié)都解析完了套菜!

總結(jié)

  • 本文對class文件進(jìn)行了逐字節(jié)分析,做了javap類似的工作设易,也得到了相符的結(jié)果逗柴。
  • Cafe babe很有趣,Java的設(shè)計者們太會了顿肺!
  • 各種定長結(jié)構(gòu)體戏溺、變長結(jié)構(gòu)體設(shè)計得非常巧妙,空間很緊湊屠尊,一點(diǎn)都沒浪費(fèi)旷祸。
  • 常量池非常重要,字節(jié)碼指令中包含對常量池的引用讼昆,常量池中的數(shù)據(jù)也有遞歸引用托享。
  • 方法中的代碼由一段Code屬性描述,它就是一個字節(jié)數(shù)組浸赫。
  • 構(gòu)造方法的名稱為<init>闰围。

了解了class文件結(jié)構(gòu)之后,就要開始探索類加載機(jī)制了既峡。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末羡榴,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子运敢,更是在濱河造成了極大的恐慌校仑,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件传惠,死亡現(xiàn)場離奇詭異迄沫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)卦方,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門羊瘩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人愿汰,你說我怎么就攤上這事困后。” “怎么了衬廷?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵摇予,是天一觀的道長。 經(jīng)常有香客問我吗跋,道長侧戴,這世上最難降的妖魔是什么宁昭? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮酗宋,結(jié)果婚禮上积仗,老公的妹妹穿的比我還像新娘。我一直安慰自己蜕猫,他們只是感情好寂曹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著回右,像睡著了一般隆圆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上翔烁,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天渺氧,我揣著相機(jī)與錄音,去河邊找鬼蹬屹。 笑死侣背,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的慨默。 我是一名探鬼主播泽谨,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼津函,長吁一口氣:“原來是場噩夢啊……” “哼搔啊!你這毒婦竟也來了匆赃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蒜胖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后抛蚤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體台谢,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年岁经,在試婚紗的時候發(fā)現(xiàn)自己被綠了朋沮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡缀壤,死狀恐怖樊拓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情塘慕,我是刑警寧澤筋夏,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站图呢,受9級特大地震影響条篷,放射性物質(zhì)發(fā)生泄漏骗随。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一赴叹、第九天 我趴在偏房一處隱蔽的房頂上張望鸿染。 院中可真熱鬧,春花似錦乞巧、人聲如沸涨椒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丢烘。三九已至,卻和暖如春些椒,著一層夾襖步出監(jiān)牢的瞬間播瞳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工免糕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赢乓,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓石窑,卻偏偏與公主長得像牌芋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子松逊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350