Java代碼編譯后的class文件

還是熟悉的味道,還是最簡單的代碼。

// Hello.java
public class Hello {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

使用javac編譯 .java 代碼,得到同名的 .class文件,然后使用 java 命令,執行類名就可以運行了。

$ javac Hello.java
$ java Hello
Hello World!

編譯后的class文件,以十六進制格式顯示,長這個樣子:

cafe babe 0000 0034 001d 0a00 0600 0f09
0010 0011 0800 120a 0013 0014 0700 1507
0016 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0a53 6f75
7263 6546 696c 6501 000a 4865 6c6c 6f2e
6a61 7661 0c00 0700 0807 0017 0c00 1800
1901 000c 4865 6c6c 6f20 576f 726c 6421
0700 1a0c 001b 001c 0100 0548 656c 6c6f
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7401 0010 6a61 7661 2f6c 616e 672f
5379 7374 656d 0100 036f 7574 0100 154c
6a61 7661 2f69 6f2f 5072 696e 7453 7472
6561 6d3b 0100 136a 6176 612f 696f 2f50
7269 6e74 5374 7265 616d 0100 0770 7269
6e74 6c6e 0100 1528 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 2956 0021 0005
0006 0000 0000 0002 0001 0007 0008 0001
0009 0000 001d 0001 0001 0000 0005 2ab7
0001 b100 0000 0100 0a00 0000 0600 0100
0000 0100 0900 0b00 0c00 0100 0900 0000
2500 0200 0100 0000 09b2 0002 1203 b600
04b1 0000 0001 000a 0000 000a 0002 0000
0003 0008 0004 0001 000d 0000 0002 000e

這就是可供Java虛擬機執行的字節碼文件,也是Java之所以能夠實現,一次編譯多次運行的根本原因。

把代碼編譯成一個中間狀態的字節碼,而不是直接運行在操作系統上的機器碼,使得跨系統的工作就完全交給Java虛擬機了。這樣一來,就可以大大減少開發人員的適配工作量,從而提高開發效率,這就是所謂的平臺無關性

采用Java虛擬機的架構設計,為語言擴展留下了空間,相當于給代碼編譯和操作系統做了一個中間件,只要將代碼編譯成字節碼文件,就都可以運行在Java虛擬機上,這也是為什么會有Scala 、Kotlin、Groovy等多種語言都可以與Java混合編碼的原因,這就是所謂的語言無關性

既然我們看到了這個字節碼文件,那就得好好解讀一下了。其實只要是代碼編譯的文件,都是有約定的規范格式的,字節碼也是一樣的。

1. class文件結構

類型 名稱 說明 備注或示例對照
u4 magic 魔數,識別class文件 cafe babe
u2 minor_version 副版本號 0000
u2 major_version 主版本號 0034
u2 constant_pool_count 常量池計數器 001d,十進制值為29
cp_info constant_pool 常量池 常量個數為constant_pool_count-1,
示例即為28個常量
u2 access_flags 訪問標志 0021
u2 this_class 類索引 0005
u2 super_class 父類索引 0006
u2 interfaces_count 接口計數器 0000
u2 interfaces 接口索引集合
u2 fields_count 字段個數 0000
field_info fields 字段集合
u2 methods_count 方法計數器 0002
method_info methods 方法集合
u2 attributes_count 附加屬性計數器 0001
attribute_info attributes 附加屬性集合 000d 0000 0002 000e

class文件偽結構中只有兩種數據類型:無符號數和表。

無符號數屬于基本的數據類型,以u1、u2、u4和u8來分表代表1個字節、2個字節、4個字節和8個字節的無符號數,無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。

是由多個無符號數或者其他表作為數據項構成的復合數據類型,所有表都習慣地以"_info"結尾。表用于描述有層次關系的復合結構的數據,而整個class文件本質上就是一張表

2. 常量類型

類型 標志 描述
CONSTANT_Utf8_info 1 UTF-8編碼的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮點型字面量
CONSTANT_Long_info 5 長整型字面量
CONSTANT_Double_info 6 雙精度浮點型字面量
CONSTANT_Class_info 7 類或接口的符號引用
CONSTANT_String_info 8 字符串類型字面量
CONSTANT_Fieldref_info 9 字段的符號引用
CONSTANT_Methodref_info 10 類中方法的符號引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符號引用
CONSTANT_NameAndType_info 12 字段或方法的符號引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MothodType_info 16 標識方法類型
CONSTANT_InvokeDynamic_info 18 表示一個動態方法調用點

3. 常量結構

常量 項目 類型 描述
CONSTANT_Utf8_info tag u1 值為1
length u2 utf-8編碼的字符串占用的字節數
bytes u1 長度為length的utf-8編碼的字符串
CONSTANT_Integer_info tag u1 值為3
bytes u4 按照高位在前存儲的int值
CONSTANT_Float_info tag u1 值為4
bytes u4 按照高位在前存儲的floatt值
CONSTANT_Long_info tag u1 值為5
bytes u8 按照高位在前存儲的long值
CONSTANT_Double_info tag u1 值為6
bytes u8 按照高位在前存儲double值
CONSTANT_Class_info tag u1 值為7
index u2 指向全限定名常量的索引
CONSTANT_String_info tag u1 值為8
index u2 指向字符串字面量的索引
CONSTANT_Fieldref_info tag u1 值為9
index u2 指向聲明字段的類或者接口描述符CONSTANT_Class_info的索引項
index u2 指向字段描述符CONSTANT_NameAndType_info的索引項
CONSTANT_Methodref_info tag u1 值為10
index u2 指向聲明方法的類描述符CONSTANT_Class_info的索引項
index u2 指向名稱及類型描述符CONSTANT_NameAndType_info的索引項
CONSTANT_InterfaceMethodref_info tag u1 值為11
index u2 指向聲明方法的接口描述符CONSTANT_Class_info的索引項
index u2 指向名稱及類型描述符CONSTANT_NameAndType_info的索引項
CONSTANT_NameAndType_info tag u1 值為12
index u2 指向該字段或方法名稱常量項的索引
index u2 指向該字段或方法描述符常量項的索引
CONSTANT_MethodHandle_info tag u1 值為15
reference_kind u1 值必須是1-9,它決定了方法句柄的類型,方法句柄類型的值表示方法句柄的字節碼行為
reference_index u2 值必須是對常量池的有效索引
CONSTANT_MothodType_info tag u1 值為16
descriptor_index u2 值必須是對常量池的有效索引,常量池在該索引處的項必須是CONSTANT_Utf8_info結構,表示方法的描述符
CONSTANT_InvokeDynamic_info tag u1 值為18
bootstrap_method_attr_index u2 值必須是對當前Class文件中引導方法表示的bootstrap_methods[]數組的有效索引
name_and_type_index u2 值必須是對當前常量池的有效索引,常量池在該索引處的項必須是CONSTANT_NameAndType_info結構,表示方法名和方法描述符

人工解讀示例字節碼中的常量池部分

0a 0013 0014 0700 1507
0016 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 046d 6169
6e01 0016 285b 4c6a 6176 612f 6c61 6e67
2f53 7472 696e 673b 2956 0100 0a53 6f75
7263 6546 696c 6501 000a 4865 6c6c 6f2e
6a61 7661 0c00 0700 0807 0017 0c00 1800
1901 000c 4865 6c6c 6f20 576f 726c 6421
0700 1a0c 001b 001c 0100 0548 656c 6c6f
0100 106a 6176 612f 6c61 6e67 2f4f 626a
6563 7401 0010 6a61 7661 2f6c 616e 672f
5379 7374 656d 0100 036f 7574 0100 154c
6a61 7661 2f69 6f2f 5072 696e 7453 7472
6561 6d3b 0100 136a 6176 612f 696f 2f50
7269 6e74 5374 7265 616d 0100 0770 7269
6e74 6c6e 0100 1528 4c6a 6176 612f 6c61
6e67 2f53 7472 696e 673b 2956
index 十六進制碼 長度 類型或備注
1 0a 1 CONSTANT_Methodref_info
0006 2 #6
000f 2 #15
2 09 1 CONSTANT_Fieldref_info
0010 2 #10
0011 2 #11
3 08 1 CONSTANT_String_info
0012 2 #18
4 0a 1 CONSTANT_Methodref_info
0013 2 #19
0014 2 #20
5 07 1 CONSTANT_Class_info
0015 2 #21
6 07 1 CONSTANT_Class_info
0016 2 #22
7 01 1 CONSTANT_Utf8_info
0006 2 length=6
3c 696e 6974 3e 6 <init>
8 01 1 CONSTANT_Utf8_info
0003 2 length=3
2829 56 3 ()V
9 01 1 CONSTANT_Utf8_info
0004 2 length=4
436f 6465 4 Code
10 01 1 CONSTANT_Utf8_info
000f 2 length=15
4c 696e 654e 756d 6265 7254 6162 6c65 15 LineNumberTable
11 01 1 CONSTANT_Utf8_info
0004 2
6d 6169 6e 4 main
12 01 1 CONSTANT_Utf8_info
0016 2
285b 4c6a 6176 612f 6c61 6e672f53 7472 696e 673b 2956 22 ([Ljava/lang/String;)V
13 01 1 CONSTANT_Utf8_info
000a 2
53 6f757263 6546 696c 65 10 SourceFile
14 01 1 CONSTANT_Utf8_info
000a 2
4865 6c6c 6f2e 6a61 7661 10 Hello.java
15 0c 1 CONSTANT_NameAndType_info
0007 2
0008 2
16 07 1 CONSTANT_Class_info
0017 2
17 0c 1 CONSTANT_NameAndType_info
0018 2
0019 2
18 01 1 CONSTANT_Utf8_info
000c 2
4865 6c6c 6f20 576f 726c 6421 12 Hello World!
19 07 1 CONSTANT_Class_info
001a 2
20 0c 1 CONSTANT_NameAndType_info
001b 2
001c 2
21 01 1 CONSTANT_Utf8_info
0005 2
48 656c 6c6f 5 Hello
22 01 1 CONSTANT_Utf8_info
0010 2
6a 6176 612f 6c61 6e67 2f4f 626a 6563 74 16 java/lang/Object
23 01 1 CONSTANT_Utf8_info
0010 2
6a61 7661 2f6c 616e 672f 5379 7374 656d 16 java/lang/System
24 01 1 CONSTANT_Utf8_info
0003 2
6f 7574 3 out
25 01 1 CONSTANT_Utf8_info
0015 2
4c 6a61 7661 2f69 6f2f 5072 696e 7453 74726561 6d3b 21 Ljava/io/PrintStream;
26 01 1 CONSTANT_Utf8_info
0013 2
6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 19 java/io/PrintStream
27 01 1 CONSTANT_Utf8_info
0007 2
70 7269 6e74 6c6e 7 println
28 01 1 CONSTANT_Utf8_info
0015 2
28 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 21 (Ljava/lang/String;)V

4. 類訪問標志

標志名稱 標志值 含義
ACC_PUBLIC 0x0001 是否為public類型
ACC_FINAL 0x0010 是否被聲明為final,只有類可以設置
ACC_SUPER 0x0020 是否允許使用invokespecial字節碼指令的新語義,JDK1.0.2之后編譯出來的類的這個標志默認為真
ACC_INTERFACE 0x0200 標志這是一個接口
ACC_ABSTRACT 0x0400 是否為abstract類型,對于接口或者抽象類來說,此標志值為真,其他類型為假
ACC_SYNTHETIC 0x1000 標志這個類并非由用戶代碼產生
ACC_ANNOTATION 0x2000 標志這是一個注解
ACC_ENUM 0x4000 標志這是一個枚舉

5. 方法表結構

類型 名稱 數量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

6. 方法訪問標志

標志名稱 標志值 含義
ACC_PUBLIC 0x0001 方法是否為public
ACC_PRIVATE 0x0002 方法是否為private
ACC_PROTECTED 0x0004 方法是否為protected
ACC_STATIC 0x0008 方法是否為static
ACC_FINAL 0x0010 方法是否為final
ACC_SYHCHRONRIZED 0x0020 方法是否為synchronized
ACC_BRIDGE 0x0040 方法是否是有編譯器產生的方法
ACC_VARARGS 0x0080 方法是否接受參數
ACC_NATIVE 0x0100 方法是否為native
ACC_ABSTRACT 0x0400 方法是否為abstract
ACC_STRICTFP 0x0800 方法是否為strictfp
ACC_SYNTHETIC 0x1000 方法是否是有編譯器自動產生的

7. 通用屬性結構表

類型 名稱 數量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length

人工解讀示例字節碼中的方法部分

0002 0001 0007 0008 0001
0009 0000 001d 0001 0001 0000 0005 2ab7
0001 b100 0000 0100 0a00 0000 0600 0100
0000 0100 0900 0b00 0c00 0100 0900 0000
2500 0200 0100 0000 09b2 0002 1203 b600
04b1 0000 0001 000a 0000 000a 0002 0000
0003 0008 0004
十六進制碼 類型或備注
0002 方法個數為2
第一個方法 0001 方法標志為public
0007 方法名索引為7, <init>
0008 方法描述符索引為8,()V
0001 attribute個數為1
第一個屬性 0009 屬性索引為9, Code
0000 001d 屬性值長度為29
0001 0001 0000 0005 2ab7 0001 b100 0000 0100 0a00 0000 0600 0100 0000 01 29個字節
第二個方法 00 09 方法標志為public,static
00 0b 方法名索引為11,main
00 0c 方法描述符索引為12,([Ljava/lang/String;)V
00 01 attribute個數為1
第一個屬性 00 09 屬性索引為9, Code
00 0000 25 屬性值長度為37
00 0200 0100 0000 09b2 0002 1203 b600 04b1 0000 0001 000a 0000 000a 0002 0000 0003 0008 0004 37個字節

屬性類型比較多,字段、接口和方法中都存在,但是都可以按照通用結構進行字節拆分。各個屬性有自己的表結構,不做逐一列舉,只列出常用的Code結構做栗子。

8. Code屬性結構表

類型 名稱 數量
u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stack 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_table_length
u2 attributes_count 1
attribute_info attributes attributes_count

逐字節解讀雖然繁瑣,但是能加深對字節碼的結構的理解。不過,在掌握了結構原理之后,使用工具才是最實在的,畢竟人工解讀的效率太低了。

$ javap -verbose Hello.class
Classfile /Users/cage/Study/java_cmd/Hello.class
  Last modified 2020-3-15; size 416 bytes
  MD5 checksum e2e1c1e330c7df4592041d815080126b
  Compiled from "Hello.java"
public class Hello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // Hello World!
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // Hello
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Hello.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               Hello World!
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               Hello
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public Hello();
    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 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello World!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
}
SourceFile: "Hello.java"

有興趣的同學,平時可以經常看看字節碼文件及其結構,看都用到了哪些字節碼指令,這樣對理解Java代碼的執行會更有好處。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372