天天看點

JVM系列十一(Class 檔案結構).

一、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 虛拟機規範》。