文章目錄
- 基礎信息
- 常量池
- 字段
- 方法
- 屬性
- 字節碼文件內容說明案例
- 文件基本信息
- 類的基本信息
- 常量池
- 字段信息
- 構造方法
- 實例方法
- 主方法
- 源文件信息
字節碼文件由五部分組成,分別是基礎信息、常量池、字段、方法、屬性。
案例:
public class Main implements InterfaceA {public static final String HELLO = "hello";public static final String WORLD = "world";public static final String HELLO2 = "hello";public static final String hello = "hello";public void methodA() {String all = HELLO + WORLD;System.out.println(all);}public static void main(String[] args) {Main main = new Main();main.methodA();}}
以下將以上述內容為案例,說明字節碼文件的五部分內容。
基礎信息
基礎信息中包含魔數、字節碼文件對應的 Java 版本號、訪問標識(public
、final
等等)及父類和接口。
- 魔數:魔數用于標識當前文件是 java 語言的字節碼文件(.class),其固定值為
0xCAFEBABE
。 - 主/次版本號:主/次版本號確定當前字節碼文件對應的 JDK 版本號,用于判斷當前字節碼文件的版本與運行該字節碼文件的 JDK 的版本是否兼容。其中,主版本號用來標識大版本號,例如 JDK1.0-1.1 使用了 45.0-45.3,JDK1.2 是 46 之后每升級一個大版本就加 1;副版本號是當主版本號相同時作為區分不同版本的標識,一般只需要關心主版本號。
使用 jclasslib 打開 Main.class
查看一般信息:
常量池
常量池中保存字符串常量、類或接口名、字段名,這些內容主要在字節碼指令中使用。常量池可以避免內容重復定義,減小 .class 文件的大小,從而達到節省空間的目的。
例如,使用 jclasslib 打開 Main.class
查看常量池中 String
類型的常量發現只有 3 個:
打開這 3 個常量信息,發現它們的值分別是 helloworld
、hello
和 world
:
String
類型的常量只有 3 個而不是 5 個的原因是成員變量 HELLO
、HELLO2
與 hello
都指向常量池中的同一個值 hello
。
需要注意的是,類實現的接口信息也存放于常量池中:
字段
字段中包含當前類或接口聲明的字段信息。
使用 jclasslib 打開 Main.class
可以查看定義了 4 個字段信息:
而其中 3 個指向了常量池中的同一個值為 hello
的地址:
即上文提到的常量池避免內容重復定義。
方法
方法中包含當前類或接口聲明的方法信息。
使用 jclasslib 打開 Main.class
查看方法信息:
屬性
屬性中包含類的屬性,比如源碼的文件名、內部類的列表等。
使用 jclasslib 打開 Main.class
查看屬性信息:
字節碼文件內容說明案例
以上述 Main.java 編譯后的字節碼文件 Main.class 為例,通過 javap -v Main.class > Main.class.txt
得到 Main.class.txt
。
Main.class.txt
內容如下:
Classfile /home/wftapp/test/Main.classLast modified Aug 6, 2025; size 814 bytesMD5 checksum ccde33e85e9b2fe106990c9f5c38f6bcCompiled from "Main.java"
public class Main implements InterfaceAminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref #8.#33 // java/lang/Object."<init>":()V#2 = Class #34 // Main#3 = String #35 // helloworld#4 = Fieldref #36.#37 // java/lang/System.out:Ljava/io/PrintStream;#5 = Methodref #38.#39 // java/io/PrintStream.println:(Ljava/lang/String;)V#6 = Methodref #2.#33 // Main."<init>":()V#7 = Methodref #2.#40 // Main.methodA:()V#8 = Class #41 // java/lang/Object#9 = Class #42 // InterfaceA#10 = Utf8 HELLO#11 = Utf8 Ljava/lang/String;#12 = Utf8 ConstantValue#13 = String #17 // hello#14 = Utf8 WORLD#15 = String #43 // world#16 = Utf8 HELLO2#17 = Utf8 hello#18 = Utf8 <init>#19 = Utf8 ()V#20 = Utf8 Code#21 = Utf8 LineNumberTable#22 = Utf8 LocalVariableTable#23 = Utf8 this#24 = Utf8 LMain;#25 = Utf8 methodA#26 = Utf8 all#27 = Utf8 main#28 = Utf8 ([Ljava/lang/String;)V#29 = Utf8 args#30 = Utf8 [Ljava/lang/String;#31 = Utf8 SourceFile#32 = Utf8 Main.java#33 = NameAndType #18:#19 // "<init>":()V#34 = Utf8 Main#35 = Utf8 helloworld#36 = Class #44 // java/lang/System#37 = NameAndType #45:#46 // out:Ljava/io/PrintStream;#38 = Class #47 // java/io/PrintStream#39 = NameAndType #48:#49 // println:(Ljava/lang/String;)V#40 = NameAndType #25:#19 // methodA:()V#41 = Utf8 java/lang/Object#42 = Utf8 InterfaceA#43 = Utf8 world#44 = Utf8 java/lang/System#45 = Utf8 out#46 = Utf8 Ljava/io/PrintStream;#47 = Utf8 java/io/PrintStream#48 = Utf8 println#49 = Utf8 (Ljava/lang/String;)V
{public static final java.lang.String HELLO;descriptor: Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATIC, ACC_FINALConstantValue: String hellopublic static final java.lang.String WORLD;descriptor: Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATIC, ACC_FINALConstantValue: String worldpublic static final java.lang.String HELLO2;descriptor: Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATIC, ACC_FINALConstantValue: String hellopublic static final java.lang.String hello;descriptor: Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATIC, ACC_FINALConstantValue: String hellopublic Main();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this LMain;public void methodA();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=2, args_size=10: ldc #3 // String helloworld2: astore_13: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;6: aload_17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V10: returnLineNumberTable:line 12: 0line 13: 3line 14: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this LMain;3 8 1 all Ljava/lang/String;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new #2 // class Main3: dup4: invokespecial #6 // Method "<init>":()V7: astore_18: aload_19: invokevirtual #7 // Method methodA:()V12: returnLineNumberTable:line 17: 0line 18: 8line 19: 12LocalVariableTable:Start Length Slot Name Signature0 13 0 args [Ljava/lang/String;8 5 1 main LMain;
}
SourceFile: "Main.java"
Main.class.txt
的各個組成部分的詳細解釋如下。
文件基本信息
Classfile /home/wftapp/test/Main.classLast modified Aug 6, 2025; size 814 bytesMD5 checksum ccde33e85e9b2fe106990c9f5c38f6bcCompiled from "Main.java"
- Classfile:指定了字節碼文件的路徑。
- Last modified:文件的最后修改時間。
- size:文件的大小,單位是字節。
- MD5 checksum:文件的 MD5 校驗和,用于驗證文件的完整性。
- Compiled from:該字節碼文件是由哪個 Java 源文件編譯而來的。
類的基本信息
public class Main implements InterfaceAminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
- public class Main implements InterfaceA:聲明了一個公共類
Main
,該類實現了接口InterfaceA
。 - minor version:次要版本號,這里是 0。
- major version:主要版本號,52 對應 Java 8。
- flags:類的訪問標志,
ACC_PUBLIC
表示該類是公共的,ACC_SUPER
是一個歷史遺留標志,現代 Java 編譯器都會設置這個標志。
常量池
Constant pool:#1 = Methodref #8.#33 // java/lang/Object."<init>":()V#2 = Class #34 // Main#3 = String #35 // helloworld...
常量池是字節碼文件的重要組成部分,它包含了類、方法、字段等的符號引用和字面量。每個常量都有一個唯一的索引,通過這些索引可以在字節碼中引用常量。例如,#1
是一個方法引用,指向 java/lang/Object
類的構造方法。
字段信息
{public static final java.lang.String HELLO;descriptor: Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATIC, ACC_FINALConstantValue: String hellopublic static final java.lang.String WORLD;...
聲明了幾個公共靜態常量字段,如 HELLO
、WORLD
等。
- descriptor:字段的類型描述符,
Ljava/lang/String;
表示該字段是String
類型。 - flags:字段的訪問標志,
ACC_PUBLIC
表示公共的,ACC_STATIC
表示靜態的,ACC_FINAL
表示常量。 - ConstantValue:常量字段的值,這里
HELLO
的值是"hello"
。
構造方法
public Main();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this LMain;
- public Main():公共的無參構造方法。
- descriptor:方法的描述符,
()V
表示該方法沒有參數,返回值為void
。 - flags:方法的訪問標志,
ACC_PUBLIC
表示公共的。 - Code:方法的字節碼指令,這里調用了父類
Object
的構造方法。 - LineNumberTable:行號表,用于將字節碼指令與源文件的行號對應起來。
- LocalVariableTable:局部變量表,記錄了方法中局部變量的信息,這里
this
是Main
類的實例。
實例方法
public void methodA();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=2, args_size=10: ldc #3 // String helloworld2: astore_13: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;6: aload_17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V10: returnLineNumberTable:line 12: 0line 13: 3line 14: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this LMain;3 8 1 all Ljava/lang/String;
- public void methodA():公共的實例方法,沒有參數,返回值為
void
。 - Code:方法的字節碼指令,這里將字符串
"helloworld"
壓入棧,然后調用System.out.println
方法打印該字符串。 - LineNumberTable:行號表,將字節碼指令與源文件的行號對應。
- LocalVariableTable:局部變量表,記錄了方法中局部變量的信息,
this
是Main
類的實例,all
是存儲"helloworld"
的局部變量。
主方法
public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new #2 // class Main3: dup4: invokespecial #6 // Method "<init>":()V7: astore_18: aload_19: invokevirtual #7 // Method methodA:()V12: returnLineNumberTable:line 17: 0line 18: 8line 19: 12LocalVariableTable:Start Length Slot Name Signature0 13 0 args [Ljava/lang/String;8 5 1 main LMain;
- public static void main(java.lang.String[]):Java 程序的入口方法。
- Code:方法的字節碼指令,這里創建了一個
Main
類的實例,然后調用該實例的methodA
方法。 - LineNumberTable:行號表,將字節碼指令與源文件的行號對應。
- LocalVariableTable:局部變量表,記錄了方法中局部變量的信息,
args
是命令行參數數組,main
是Main
類的實例。
源文件信息
SourceFile: "Main.java"
指定了該字節碼文件對應的源文件名稱。