《深入理解 Java 虛擬機》是 Java 開發者進階必讀的經典著作。本文作為閱讀筆記第一部分,重點梳理 Class 文件的結構組成、核心組件以及通過實例分析字節碼結構,幫助開發者建立對 JVM 執行基礎的深度認知。
Java 源代碼從編寫到運行,經歷了標準化的轉換鏈路:
.java 源文件 → javac 編譯器 → .class 字節碼文件 → JVM(如 HotSpot)執行
這一過程體現了 Java「一次編寫,到處運行」的核心機制——編譯器屏蔽了平台差異,統一輸出符合 JVM 規範的字節碼。
Class 文件採用類似 C 語言結構體的順序緊湊存儲格式,主要包含以下七大核心區域:
以三個關聯類為例:Father.java、Father_interface.java、Son.java,其中 Son 繼承 Father 並實現 Father_interface。
cd 桌面/test
javac Son.java
javap -verbose Son
Classfile /C:/Users/Administrator/Desktop/test/Son.class Last modified 2016-4-11; size 1005 bytes MD5 checksum 7d138d76267a6bb372eba05296e2c932 魔數 Compiled from "Son.java" public class test.Son extends test.Father implements test.Father_interface 索引(本類、父類、接口,均指向常量池) SourceFile: "Son.java" minor version: 0 major version: 51 主版本號 flags: ACC_PUBLIC, ACC_SUPER 訪問標志 Constant pool: 常量池 #1 = Methodref #16.#33 // test/Father."":()V 符號引用 #2 = Fieldref #15.#34 // test/Son.sId_literal:I #3 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream; #4 = Class #37 // java/lang/StringBuilder #5 = Methodref #4.#33 // java/lang/StringBuilder." ":()V #6 = String #38 // statical_sId = #7 = Methodref #4.#39 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #8 = Fieldref #15.#40 // test/Son.statical_sId:I #9 = Methodref #4.#41 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; #10 = Methodref #4.#42 // java/lang/StringBuilder.toString:()Ljava/lang/String; #11 = Methodref #43.#44 // java/io/PrintStream.println:(Ljava/lang/String;)V #12 = String #45 // Son實例構造器! #13 = String #46 // hello!this is son! #14 = String #47 // Son類構造器 執行! #15 = Class #48 // test/Son #16 = Class #49 // test/Father #17 = Class #50 // test/Father_interface #18 = Utf8 final_sId #19 = Utf8 I #20 = Utf8 ConstantValue #21 = Integer 123 字面量 #22 = Utf8 statical_sId #23 = Utf8 sId_literal #24 = Utf8 sId_nonLiteral #25 = Utf8 #26 = Utf8 ()V #27 = Utf8 Code #28 = Utf8 LineNumberTable #29 = Utf8 sayHello #30 = Utf8 #31 = Utf8 SourceFile #32 = Utf8 Son.java #33 = NameAndType #25:#26 // " ":()V #34 = NameAndType #23:#19 // sId_literal:I #35 = Class #51 // java/lang/System #36 = NameAndType #52:#53 // out:Ljava/io/PrintStream; #37 = Utf8 java/lang/StringBuilder #38 = Utf8 statical_sId = #39 = NameAndType #54:#55 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #40 = NameAndType #22:#19 // statical_sId:I #41 = NameAndType #54:#56 // append:(I)Ljava/lang/StringBuilder; #42 = NameAndType #57:#58 // toString:()Ljava/lang/String; #43 = Class #59 // java/io/PrintStream #44 = NameAndType #60:#61 // println:(Ljava/lang/String;)V #45 = Utf8 Son實例構造器! #46 = Utf8 hello!this is son! #47 = Utf8 Son類構造器 執行! #48 = Utf8 test/Son #49 = Utf8 test/Father #50 = Utf8 test/Father_interface #51 = Utf8 java/lang/System #52 = Utf8 out #53 = Utf8 Ljava/io/PrintStream; #54 = Utf8 append #55 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #56 = Utf8 (I)Ljava/lang/StringBuilder; #57 = Utf8 toString #58 = Utf8 ()Ljava/lang/String; #59 = Utf8 java/io/PrintStream #60 = Utf8 println #61 = Utf8 (Ljava/lang/String;)V { public test.Son(); 方法表——實例構造器 flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method test/Father." ":()V 4: aload_0 5: bipush 123 7: putfield #2 // Field sId_literal:I 10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 13: new #4 // class java/lang/StringBuilder 16: dup 17: invokespecial #5 // Method java/lang/StringBuilder." ":()V 20: ldc #6 // String statical_sId = 22: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 25: getstatic #8 // Field statical_sId:I 28: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 31: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 34: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 37: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 40: ldc #12 // String Son實例構造器! 42: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 45: sipush 333 48: putstatic #8 // Field statical_sId:I 51: return LineNumberTable: line 15: 0 line 7: 4 line 16: 10 line 17: 37 line 18: 45 line 19: 51 public void sayHello(); 方法表——sayHello() flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #13 // String hello!this is son! 5: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 21: 0 line 22: 8 static {}; 方法表——類構造器(靜態代碼塊) flags: ACC_STATIC Code: stack=3, locals=0, args_size=0 0: bipush 123 2: putstatic #8 // Field statical_sId:I 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 8: new #4 // class java/lang/StringBuilder 11: dup 12: invokespecial #5 // Method java/lang/StringBuilder." ":()V 15: ldc #6 // String statical_sId = 17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 20: getstatic #8 // Field statical_sId:I 23: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 26: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 29: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 32: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 35: ldc #14 // String Son類構造器 執行! 37: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 40: return LineNumberTable: line 6: 0 line 11: 5 line 12: 32 line 13: 40 }
常量池是 Class 文件中最關鍵、佔用空間最大的區域,分為兩大類:
int、double 等)test/Son)sId_literal:I):()V )三類索引均指向常量池中的 CONSTANT_Class_info 項:
test/Sontest/Fathertest/Father_interface每張表項包含:
Code、LineNumberTable、LocalVariableTable、Exceptions 等)| 組件 | 核心作用 |
|---|---|
| 魔數 | 確認文件格式(0xCAFEBABE),防止後綴名修改欺騙 |
| 版本號 | 主版本號對應 JDK 版本(45=1.1, 46=1.2, 47=1.3, 48=1.4, 49=1.5, 50=1.6, 51=1.7...) |
| 常量池 | 存放字面量與符號引用,是動態連接的關鍵 |
| 訪問標志 | 識別類/接口屬性(ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_INTERFACE, ACC_ABSTRACT 等) |
| 索引集合 | 確定類繼承體系(本類、父類、接口) |
| 字段表集合 | 描述類級/實例級變量完整元數據 |
| 方法表集合 | 包含字節碼指令、異常處理、調試信息等執行核心 |
Class 文件結構是 JVM 執行引擎的「輸入語言」。掌握其組成邏輯,不僅有助於理解類加載、字節碼執行、動態代理等進階機制,更是排查 NoClassDefFoundError、IncompatibleClassChangeError 等疑難雜症的基礎。建議開發者熟練使用 javap -verbose 反編譯工具,養成「讀字節碼」的習慣,將技術深度從 API 調用下沉到虛擬機原理層面。