JVM虛擬機Class類文件研究分析

前言

爲了研究Class文件,先編寫一個最簡單的代碼:java

package com.courage;
public class T0100_ByteCode01 {
}

之因此說最簡單,是由於這個類裏面任何方法,變量都沒有,看看編譯以後Class文件的16進制代碼:安全

2002319-20210201175641063-896578792

在解讀上面的Class文件(後面沒有特殊生命的話都是指16進制)以前,須要先學習幾個前置知識,Java 虛擬機規範規定 Class 文件格式採用一種相似與 C 語言結構體的微結構體來存儲數據,這種僞結構體中只有兩種數據類型:無符號數和表。學習

  • 無符號數屬於基本的數據類型,以 u一、u二、u四、u8來分別表明 1 個字節、2 個字節、4 個字節和 8 個字節的無符號數,無符號數能夠用來描述數字、索引引用、數量值或者按照 UTF-8 編碼結構構成的字符串值。
  • 是由多個無符號數或者其餘表做爲數據項構成的複合數據類型,全部表都習慣性地以「_info」結尾。表用於描述有層次關係的複合結構的數據,整個 Class 文件就是一張表,它由下表中所示的數據項構成。

有了無符號數這個概念,就能夠根據虛擬機規範來解讀上面的文件了:this

類型 名稱 含義 數量
u4 magic 魔數,不變 1
u2 minor_version 小版本號:JDK 8_255u中的255u 1
u2 major_version 大版本號,JDK 8_255u中的8 1
u2 constant_pool_count 常量池數量 1
cp_info constant_pool 常量池 constant_pool_count-1
u2 access_flags 訪問修飾符 public static 等 1
u2 this_class 當前類 1
u2 super_class 父類 1
u2 interfaces_count 接口數量 1
u2 interfaces 接口 interfaces_count
u2 fields_count 變量數量 1
field_info fields 變量 fields_count
u2 methods_count 方法數量 1
method_info methods 方法 methods_count
u2 attributes_count 屬性數量 1
attribute_info attributes 屬性 attributes_count

全部的Class文件裏面的屬性都按照上表的規則排序,中間沒有空行或其餘轉義字符。哪一個字節表明什麼含義,長度是多少,前後順序如何都是被嚴格限制的,不容許有任何改變。編碼

魔數與 Class 文件版本

每一個 Class 文件的頭 4 個字節稱爲魔數(Magic Number),它的惟一做用是肯定這個文件是否爲一個能被虛擬機接收的 Calss 文件。之因此使用魔數而不是文件後綴名來進行識別主要是基於安全性的考慮,由於文件後綴名是能夠隨意更改的。Class 文件的魔數值爲「0xCAFEBABE」。3d

緊接着魔數的 4 個字節存儲的是 Class 文件的版本號:第 5 和第 6 兩個字節是次版本號(Minor Version),第 7 和第 8 個字節是主版本號(Major Version)。高版本的 JDK 可以向下兼容低版本的 Class 文件,虛擬機會拒絕執行超過其版本號的 Class 文件。code

常量池

主版本號以後是常量池入口,常量池能夠理解爲 Class 文件之中的資源倉庫,它是 Class 文件結構中與其餘項目關聯最多的數據類型,也是佔用 Class 文件空間最大的數據項目之一,同是它仍是 Class 文件中第一個出現的表類型數據項目。blog

由於常量池中常量的數量是不固定的,因此在常量池入口須要放置一個 u2 類型的數據來表示常量池的容量「constant_pool_count」,和計算機科學中計數的方法不同,這個容量是從 1 開始而不是從 0 開始計數。之因此將第 0 項常量空出來是爲了知足後面某些指向常量池的索引值的數據在特定狀況下須要表達「不引用任何一個常量池項目」的含義,這種狀況能夠把索引值置爲 0 來表示。排序

Class 文件結構中只有常量池的容量計數是從 1 開始的,其它集合類型,包括接口索引集合、字段表集合、方法表集合等容量計數都是從 0 開始。繼承

常量池中主要存放兩大類常量:字面量符號引用

  • 字面量比較接近 Java 語言層面的常量概念,如字符串、聲明爲 final 的常量值等。

  • 符號引用屬於編譯原理方面的概念,包括瞭如下三類常量:

    • 類和接口的全限定名
    • 字段的名稱和描述符
    • 方法的名稱和描述符

常量池17種數據類型的結構表



訪問標誌

緊接着常量池以後的兩個字節表明訪問標誌(access_flag),這個標誌用於識別一些類或者接口層次的訪問信息,包括這個 Class 是類仍是接口;是否認義爲 public 類型;是否認義爲 abstract 類型;若是是類的話,是否被申明爲 final 等。具體的標誌位以及標誌的含義見下表:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 是否爲 public 類型
ACC_FINAL 0x0010 是否被聲明爲 final,只有類可設置
ACC_SUPER 0x0020 是否容許使用 invokespecial 字節碼指令的新語意,invokespecial 指令的語意在 JKD 1.0.2 中發生過改變,微聊區別這條指令使用哪一種語意,JDK 1.0.2 編譯出來的類的這個標誌都必須爲真
ACC_INTERFACE 0x0200 標識這是一個接口
ACC_ABSTRACT 0x0400 是否爲 abstract 類型,對於接口或者抽象類來講,此標誌值爲真,其它類值爲假
ACC_SYNTHETIC 0x1000 標識這個類並不是由用戶代碼產生
ACC_ANNOTATION 0x2000 標識這是一個註解
ACC_ENUM 0x4000 標識這是一個枚舉

access_flags 中一共有 16 個標誌位可使用,當前只定義了其中的 8 個,沒有使用到的標誌位要求一概爲 0。

類索引、父類索引與接口索引集合

類索引(this_class)和父類索引(super_class)都是一個 u2 類型的數據,而接口索引集合(interfaces)是一組 u2 類型的數據集合,Class 文件中由這三項數據來肯定這個類的繼承關係。

  • 類索引用於肯定這個類的全限定名
  • 父類索引用於肯定這個類的父類的全限定名
  • 接口索引集合用於描述這個類實現了哪些接口

字段表集合

字段表集合(field_info)用於描述接口或者類中聲明的變量。字段(field)包括類變量和實例變量,但不包括方法內部聲明的局部變量。下面咱們看看字段表的結構:

類型 名稱 數量
u2 access_flag 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

字段修飾符放在 access_flags 中,它與類中的 access_flag 很是類似,都是一個 u2 的數據類型。

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 字段是否爲 public
ACC_PRIVATE 0x0002 字段是否爲 private
ACC_PROTECTED 0x0004 字段是否爲 protected
ACC_STATIC 0x0008 字段是否爲 static
ACC_FINAL 0x0010 字段是否爲 final
ACC_VOLATILE 0x0040 字段是否爲 volatile
ACC_TRANSIENT 0x0080 字段是否爲 transient
ACC_SYNTHETIC 0x1000 字段是否由編譯器自動生成
ACC_ENUM 0x4000 字段是否爲 enum

方法表集合

Class 文件中對方法的描述和對字段的描述是徹底一致的,方法表中的結構和字段表的結構同樣。

由於 volatile 關鍵字和 transient 關鍵字不能修飾方法,因此方法表的訪問標誌中沒有 ACC_VOLATILE 和 ACC_TRANSIENT。與之相對的,synchronizes、native、strictfp 和 abstract 關鍵字能夠修飾方法,因此方法表的訪問標誌中增長了 ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP 和 ACC_ABSTRACT 標誌。

對於方法裏的代碼,通過編譯器編譯成字節碼指令後,存放在方法屬性表中一個名爲「Code」的屬性裏面。

屬性表集合

在 Class 文件、字段表、方法表中均可以攜帶本身的屬性表(attribute_info)集合,用於描述某些場景專有的信息。

屬性表集合不像 Class 文件中的其它數據項要求這麼嚴格,不強制要求各屬性表的順序,而且只要不與已有屬性名重複,任何人實現的編譯器均可以向屬性表中寫入本身定義的屬性信息,Java 虛擬機在運行時會略掉它不認識的屬性。

下面就能夠對類文件逐行分析了:

相關文章
相關標籤/搜索