1. 程式人生 > 實用技巧 >類檔案結構

類檔案結構

首先實現語言無關性的基礎仍然是虛擬機器和位元組碼儲存格式.Java虛擬機器不和包括Java在內
的任何語言繫結,它只與“Class檔案”這種特定的二進位制檔案格式所關聯,Class檔案中包含
了 Java虛擬機器指令集和符號表以及若干其他輔助資訊。基於安全方面的考慮,Java虛擬機器規
範要求在Class檔案中使用許多強制性的語法和結構化約束,但任一門功能性語言都可以表示
為一個能被Java虛擬機器所接受的有效的Class檔案。作為一個通用的、機器無關的執行平臺 這一篇主要還是講有關Class檔案的結構,這是JVM深入的基礎。

Class類檔案的結構

注意點:所有的介面和類都是對應著一個Class檔案,但是並非所有的介面和類都需要以磁碟檔案的方式存在,也可以直接通過類載入器生成。 Class檔案是一組以8位元組為基礎的二進位制流,所有的資料都是存在於此處的,而且中間沒有任何分隔符等,只是簡單的壓縮在一起。當需要的時候會選擇使用8位元組進行分割。 JVM中是採用一種偽結構體的方式來儲存資料的,無符號數+表 無符號數使用u1.u2,u4,u8來代表一位元組~八位元組(表示的是位元組數,例如索引的位元組數等等) 表就是多個無符號數和多個表等符合結構組成的資料,通常以_info結尾。 例如上面第一條,magic這個可以是索引也可以是數字,數量值等,但是他是4位元組的。這一條的含義就是這樣。
常量池 常量池是Class檔案中的資源倉庫,同時也是第一個表結構的資料,也是佔用Class檔案空間最大的專案之一。 由於常量池的數量不是固定的,所有一般在第一個資料的位置會有一個u2大小的來表示下常量池的數量,但是這個不一樣的地方是從1開始,並不是同Java中從0來開始計算。這樣做的目的是為了滿足後面某些指向常量池的索引值的資料在特定情況下需要表達“不引用任何一個常量池的專案”的含義。這樣的話這些索引值直接指向0就行。 這邊就是0016,轉化為10進行就是22,所以這個常量池裡面會有21個常量。 常量池裡面放的一般有兩大類常量:字面量和符號引用 字面量:接近Java中的常量例如文字類字串,final修飾的值 符號引用有三大類:介面和類的全限定名,欄位的名稱和描述符,方法的名稱和描述符 這邊要記住一個Class檔案只對應一個類或者一個介面。 Java程式碼在Javac編譯的時候,會進行動態連結,在Class檔案中不會保留具體的引用,例如這個介面依賴的介面類的地址等等,所以需要在執行期轉換的時候得到真正的記憶體入口地址。 另外常量池的每一個常量都是一個表。

訪問標誌 訪問表示用來識別類或者介面的訪問資訊,例如這個Class是介面還是類,是否定義為public類,是否定義為abstract,如果是類的話是不是final型的。 沒有使用到的標誌位一致定義為0. 類索引,父類索引與介面索引 類索引(this_ class) 和父類索引(super_ class) 都是一個u2型別的資料,而介面索引集合(interfaces) 是一組u2型別的資料的集合,Class 檔案中由這三項資料來確定這個類的繼承關係。類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類的全限定名。由於Java語言不允許多重繼承,所以父類索引只有一個,除了java.lang Object之外,所有的Java類都有父類,因此除了java.lang.Object外,所有Java類的父類索引都不為0。介面索弓集合就用來描述這個類實現了哪些介面,這些被實現的介面將按implements 語句(如果這個類本身是-一個介面,則應當是exfends語句)後的介面順序從左到在排列在介面索引集合中。 類索引、父類索引和接日索引集合都按順序排列在訪問標誌之後
,類索引和父類索引用兩個u2型別的索引值表示,它們各自指向一個型別為CONSTANT_ Class_ info的類描述符常量,通過CONSTANT_ Class _info 型別的常量中的索引值咱以找到定義在CONSTANT_ Utf8_info型別的常量中的全限定名字元申。圖6-6演示了程式碼清單6-1的程式碼的類索引查詢過程。 欄位表集合 欄位表用於描述介面或者類中宣告的變數。欄位(filed)包括類級變數以及例項級變數,但不包括在方法內部宣告的區域性變數。可以包括的資訊例如欄位的作用域,例項變數還是類變數,可變性,併發可見性,可否被序列化,欄位資料型別,欄位名稱。 上面的這些值都是布林型別的,要麼為1要麼為0所以很適合用標誌位來進行表示。而欄位叫什麼,欄位被定義為什麼資料型別這些都是固定的,只能引用常量池裡面的常量來表示。例如下圖所示

這個很適合用access_flags標誌來表示,跟隨access_flags標誌的是兩項索引值:name_index和descriptor index.它們都是對常
最池的引用,分別代表著欄位的簡單名稱以及欄位和方法的描述符。現在需要解釋一下“簡
單名稱”、“描述符”以及前面出現過多次的“全限定名”這三種特殊字串的概念。

其餘的方法區和屬性區都是差不多的方法。


標頭檔案分析:
public class JavaTest {
    private static String name = "JVM";
    public static void main(String[] args) {
        System.out.println("Hello " + name);
    }
}

出來的標頭檔案是這樣的

每個class檔案的頭4個位元組稱為魔數,用於確定這個檔案是否能被虛擬機器所接受。class檔案的魔數值為CAFEBABE。

第5、6位元組為次版本號,7、8位元組為主版本號。Java的主版本號從45開始,JDK1.1之後每個大版本釋出,主版本號加1。高版本的jdk能前向相容之前版本的class檔案,但不能執行以後版本的class檔案。

從圖1可以看到,次版本號為0000,主版本號為0031,這說明該class檔案可以被1.5及以後版本的jdk執行。

CA FE BA BE 00 00 00 31=>202 254 186 190 00 00 00 49 1.1+0.4 = 1.5之後

這個代表了常量池中的數量,34=>52-1 = 51個

常量池中每一個常量都是一個表,jdk1.7之後一共有14種類型的常量,他們對應著14個不同結構的表,但這14個表都有一個共同特點:那就是表開始的第一位是一個u1型別的標誌位,代表當前常量屬於哪種常量型別。其取值和含義如下表所示:

例如第一個是0A,那麼對應就是10,表中為類方法的符號引用,下面一個是000C=>12.......

bogon:Downloads shiyangsheng$ javap -v JavaTest.class
Classfile /Users/shiyangsheng/Downloads/JavaTest.class
  Last modified 2018-3-17; size 842 bytes
  MD5 checksum fbb2370c6b7413a0636806a0e492224a
  Compiled from "JavaTest.java"
public class com.youzan.shys.advice.JavaTest
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#29        // java/lang/Object."<init>":()V
   #2 = Fieldref           #30.#31        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Class              #32            // java/lang/StringBuilder
   #4 = Methodref          #3.#29         // java/lang/StringBuilder."<init>":()V
   #5 = String             #33            // Hello
   #6 = Methodref          #3.#34         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #7 = Fieldref           #11.#35        // com/youzan/shys/advice/JavaTest.name:Ljava/lang/String;
   #8 = Methodref          #3.#36         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Methodref          #37.#38        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = String             #39            // JVM
  #11 = Class              #40            // com/youzan/shys/advice/JavaTest
  #12 = Class              #41            // java/lang/Object
  #13 = Utf8               name
  #14 = Utf8               Ljava/lang/String;
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lcom/youzan/shys/advice/JavaTest;
  #22 = Utf8               main
  #23 = Utf8               ([Ljava/lang/String;)V
  #24 = Utf8               args
  #25 = Utf8               [Ljava/lang/String;
  #26 = Utf8               <clinit>
  #27 = Utf8               SourceFile
  #28 = Utf8               JavaTest.java
  #29 = NameAndType        #15:#16        // "<init>":()V
  #30 = Class              #42            // java/lang/System
  #31 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #32 = Utf8               java/lang/StringBuilder
  #33 = Utf8               Hello
  #34 = NameAndType        #45:#46        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #35 = NameAndType        #13:#14        // name:Ljava/lang/String;
  #36 = NameAndType        #47:#48        // toString:()Ljava/lang/String;
  #37 = Class              #49            // java/io/PrintStream
  #38 = NameAndType        #50:#51        // println:(Ljava/lang/String;)V
  #39 = Utf8               JVM
  #40 = Utf8               com/youzan/shys/advice/JavaTest
  #41 = Utf8               java/lang/Object
  #42 = Utf8               java/lang/System
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Utf8               append
  #46 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #47 = Utf8               toString
  #48 = Utf8               ()Ljava/lang/String;
  #49 = Utf8               java/io/PrintStream
  #50 = Utf8               println
  #51 = Utf8               (Ljava/lang/String;)V

  緊接著常量池之後的兩個位元組表示訪問標誌,主要是用來標記類或者介面層次的一些屬性。目標之定義了16個標誌位中的8位,沒有使用到的一律為0。 具體標誌位如下表:

在訪問標誌之後,有3個用來確定一個類的繼承關係的資料,按先後順序分別是:

  • 類索引:用於確定類的全限定名
  • 父類索引:用於確定父類的全限定名
  • 介面索引:用於描述類實現了哪些介面
  • 000B+000C+0000=>11+12+0=>指向常量池的11 12 0=> #11 = Class #40 // com/youzan/shys/advice/JavaTest #12 = Class #41 // java/lang/Object 再後面也是一樣的分析方法具體的可以看: https://www.jianshu.com/p/0ddf610991a5這一篇的記錄,非常的詳細!