一、Class 檔案結構
JDK 的版本号已經到 14 了,相對于語言、API 以及 Java 技術體系中其他方面的變化,Class 檔案結構一直處于比較穩定的狀态,Class 檔案的主體結構、位元組碼指令的語義和數量幾乎沒有出現過變動。
Class 檔案是一組以8位位元組為基礎機關的二進制流,各個資料項目嚴格按照順序緊湊地排列在 Class 檔案中,中間沒有添加任何分隔符,這使得整個 Class 檔案中存儲的内容幾乎全部是程式運作的必要資料,沒有空隙存在。
根據 Java 虛拟機規範的規定,Class 檔案格式采用一種類似于 C 語言結構體的僞結構來存儲資料,這種僞結構隻有兩種資料類型:無符号數和表:
無符号數:以 u1、u2、u4、u8 來分别代表1個位元組、2個位元組、4個位元組和8個位元組的無符号數,無符号數可以用來描述數字、索引引用、數量值或者按照 UTF-8 編碼構成字元串值。
表:由多個無符号數或者其他表作為資料項構成的複合資料類型,所有表都習慣性的以“_info” 結尾,用于描述有層次關系的複合結構的資料,整個 Class 檔案本質上就是一張表。
下面是 Class 檔案格式:
類型 | 名稱 | 數量 | 描述 |
---|---|---|---|
u4 | magic | 1 | 表示這個檔案是否為一個能被虛拟機接受的 Class 檔案 |
u2 | minor_version | 次版本号 | |
major_version | 主版本号,Java 的版本号從45開始 | ||
constant_pool_count | 常量池容量計數值 | ||
cp_info | constant_pool | constant_pool_count-1 | 常量池是Class檔案中的資源倉庫,主要存放兩大類常量:字面量和符号引用(見下文釋義) |
access_flags | 識别類/接口層次的通路資訊,比如:這個 Class 是類還是接口,是否為 pubilc,是否為 final 等 | ||
this_class | 類索引,用于确定這個類的全限定名 | ||
super_class | 父類索引,用于确定這個類的父類的全限定名 | ||
interfaces_count | 實作的接口索引集合數量 | ||
interfaces | 用來描述這個類實作了哪些接口 | ||
fields_count | 字段數量 | ||
field_info | fields | 描述接口或者類中聲明的變量,包括類級變量以及執行個體級變量 | |
methods_count | 方法數量 | ||
method_info | methods | 描述接口或者類中聲明的方法,包括類級方法以及執行個體級方法 | |
attributes_count | 屬性數量 | ||
attribute_info | attributes | 描述字段/方法表中額外的資訊,比如 Code 用來存儲具體的代碼,ConstantValue 用于存儲常量等 |
指的是 Java 語言中的文本字元串、聲明為 final 的常量值等。而
符号引用則屬于編譯原理方面的概念,包括了下面三類常量:
- 類和接口的全限定名
- 字段的名稱或描述符
- 方法的名稱或描述符
二、Class 檔案位元組碼
public class TestClass {
private int m;
public int inc() {
return m + 1;
}
}
我們通過 JAVA_HOME/bin/javap 工具的 -verbose 參數輸出 TestClass.class 檔案的内容,用來分析 Class 檔案位元組碼。
javap -verbose TestClass
Classfile /D:/JMCui/jvm-demo/demo/target/classes/org/jvm/demo/chapter6/TestClass.class
Last modified 2020-4-1; size 397 bytes
MD5 checksum 291f52e2b746bf6c338ece68fdf3dc08
Compiled from "TestClass.java"
public class org.jvm.demo.chapter6.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant :
#1 = Methodref #4.#18 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#19 // org/jvm/demo/chapter6/TestClass.m:I
#3 = Class #20 // org/jvm/demo/chapter6/TestClass
#4 = Class #21 // java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lorg/jvm/demo/chapter6/TestClass;
#14 = Utf8 inc
#15 = Utf8 ()I
#16 = Utf8 SourceFile
#17 = Utf8 TestClass.java
#18 = NameAndType #7:#8 // "<init>":()V
#19 = NameAndType #5:#6 // m:I
#20 = Utf8 org/jvm/demo/chapter6/TestClass
#21 = Utf8 java/lang/Object
{
public org.jvm.demo.chapter6.TestClass();
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 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/jvm/demo/chapter6/TestClass;
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lorg/jvm/demo/chapter6/TestClass;
}
SourceFile: "TestClass.java"
上面的 minor version、 major version、flags、Constant 這些含義都比較好了解。
Code 屬性是 Class 檔案中最重要的一個屬性,如果把一個 Java 程式中的資訊分為代碼(Code,方法體裡面的 Java 代碼)和中繼資料(Metadata,包括類、字段、方法定義及其他資訊)兩部分,那麼整個 Class 檔案中,Code 屬性用于描述代碼,所有的其他資料項目都用來描述中繼資料。
stack、locals 是 Code 屬性中的内容,stack 表示操作數棧;locals 表示局部變量表所屬的存儲空間,機關是 Slot,Slot 是虛拟機為局部變量配置設定記憶體所使用的最小機關。
args_size 表示參數數量,上面的 args_size 之是以會為 1,是因為在執行個體方法的局部變量表中至少會存在一個指向目前對象執行個體的局部變量,局部變量表中也會預留出第一個 Slot 位來存放對象執行個體的引用。
LineNumberTable 用于描述 Java 源碼行号與位元組碼行号(位元組碼的偏移量)之間的對應關系。
LocalVariableTable 用于描述棧幀中局部變量表中的變量與 Java 源碼中定義的變量之間的關系。LocalVariableTypeTable 用于泛型類型記錄特征簽名(Signature)資訊。
SourceFile 記錄生成這個 Class 檔案的源碼檔案名稱。
aload_0、invokespecial、return 這些屬于位元組碼指令,不是本文介紹的重點,相關的位元組碼指令以及其他屬性資訊可以閱讀《Java 虛拟機規範》。