Java虛擬機器,類檔案結構深度解析

lihong發表於2019-05-14

Java類檔案結構

Java虛擬機器不和包括Java在內的任何語言繫結,只與 "Class檔案" 這種特定的二進位制檔案所關聯, Class檔案中包含了Java虛擬機器 指令集合符號表 以及若干其它輔助資訊。 Java虛擬機器作為一個通用的、機器無關的執行平臺,任何其他語言都可以將其作為語言的產品交付媒介。

Java虛擬機器,類檔案結構深度解析

 

Class類檔案結構

Class檔案是一組以8位位元組為基礎的 二進位制流 , 各個資料專案嚴格按照順序緊湊地排列在Class檔案之中,中間沒有新增任何分隔符, 這使得整個Class檔案中儲存的內容幾乎全部是程式執行的必要資料,沒有空隙存在。 當遇到需要佔用8位位元組以上空間的資料項時,則會按照 高位在前 的方式分割成若干個8位位元組進行儲存。

Class檔案格式採用一種類似於C語言結構體的偽結構來儲存數,這種偽結構有兩種資料型別:

無符號數

無符號數:屬於基本資料型別,以u1、u2、u4、u8來代表1個位元組、2個位元組、4個位元組、8個位元組的無符號數, 無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字串值。

表:由多個無符號數或者其他表作為資料項構成的複合資料型別,所有表都習慣性地以"_info"結尾。 表用於描述有層次關係的複合結構的資料,整個Class檔案本質上就是一張表。

  • 注意:Class檔案結構不像XML等描述語言,由於它沒有任何分割符號, 所以無論是數量甚至於資料儲存的位元組序這樣的細節都被嚴格限定, 哪個位元組代表什麼含義,長度是多少,先後順序如何,都不允許改變。

魔數與Class檔案版本

每個Class檔案的頭四個位元組稱為魔數(Magic Number), 它的唯一作用是 確定這個檔案是否為一個能被虛擬機器接收的Class檔案 。 緊接著魔數的四個位元組儲存的是Class檔案的版本號: 第五和第六是次版本號,第七和第八是主版本號。

Java虛擬機器,類檔案結構深度解析

 

常量池

緊接著主次版本號之後的是常量池入口, 常量池可以理解為Class檔案之中的資源倉庫 , 它是Class檔案結構中與其他專案關聯最多的資料型別,也是佔用Class檔案空間最大的資料專案之一, 同時它還是在Class檔案中第一個出現的表型別資料專案。 常量池主要存放兩大常量: 字面量 符號引用 。 字面量比較接近於java語言層面的的常量概念,如文字字串、宣告為final的常量值等。 而符號引用則屬於 編譯原理方面的概念 。包括下面三類常量:

  • 類和介面的全限定名

  • 欄位的名稱和描述符

  • 方法的名稱和描述符

由於常量池中常量的數量是不固定的,所以在常量池的入口需要設定一項u2型別的資料,代表常量池容量計數值。 與Java中語言習慣不一樣的是,這個容量技術是從1而不是0開始的,如下圖表示,常量池容量(偏移地址:0x00000008)

為十六進位制數0x0016,即十進位制22,這代表常量池中有21項常量,索引值範圍為1~21。在Class檔案格式規範制定之時,設計者將第0項常量空出來是有特殊考慮的,這樣做的目的在於 滿足後面某些指向常量池的索引值的資料在特定情況下需要表達

"不引用任何一個常量池專案"的含義,這種情況就可以把索引值置為0來表示。  Class檔案結構中只有常量池的容量計數從1開始 , 對於其他集合型別,包括介面索引結合,欄位表集合,方法表集合等容量技術都與一般習慣相同,從0開始。

Java虛擬機器,類檔案結構深度解析

 

訪問標誌

在常量池結束之後,緊接著的兩個位元組代表訪問標誌, 這個標誌用於識別一些類或者介面層次的訪問資訊, 包括:這個Class是類還是介面,是否為public或者abstract型別,如果是類的話是否宣告為final等等。 標誌位和標誌的含義對應如下:

Java虛擬機器,類檔案結構深度解析

 

類索引、父類索引與介面索引集合

類索引、父類索引與介面索引集合都按順序排列在訪問標誌之後, Class檔案由這三項資料來確定這個 類的繼承關係 。 類索引用於確定 這個類的全限定名 , 父類索引用於確定 這個類的父類的全限定名 ,由於java語言的單繼承,所以父類索引只有一個, 除了java.lang.Object之外,所有的java類都有父類,因此除了java.lang.Object外,所有java類的父類索引都不為0。 介面索引集合用來描述 這個類實現了哪些介面 ,這些被實現的介面將按 implents(如果這個類本身是介面的話則是extends)後的介面順序從左到右排列在 介面索引集合中。

欄位表集合

欄位表(field info)用於描述介面或類中宣告的變數。 欄位包括 類變數 例項變數 ,但不包括在方法內部宣告的區域性變數。

我們可以想一想在Java中描述一個欄位可以包含什麼資訊呢?

欄位的作用域(public ,private,protected修飾符),是例項變數還是類變數(static修飾符)、 可變性(final)、併發可見性(volatile修飾符,是否強制從主記憶體讀寫)、可否被序列化(transient修飾符)、 欄位資料型別、欄位名稱。

上述這些資訊中,各個修飾符都是布林值,要麼有某個修飾符,要麼沒有,很適合使用標誌位來表示。 而欄位叫什麼名字、欄位被定義為什麼資料型別這些都是無法固定的,只能引用常量池中常量來描述。

方法表集合

Class檔案儲存格式中對方法的描述與對欄位的描述幾乎採用了完全一致的方式。 方法表的結構如同欄位表一樣,依次包括了訪問標誌、名稱索引、描述符索引、屬性表集合幾項。

  • 注意:因為volatile修飾符和transient修飾符不可以修飾方法, 所以方法表的訪問標誌中沒有這兩個對應的標誌,但是增加了synchronized、native、abstract等關鍵字修飾方法, 所以也就多了這些關鍵字對應的標誌。

屬性表集合

在Class檔案, 欄位表,方法表中都可以攜帶自己的屬性表集合 ,以用於描述某些場景專有的資訊。 與Class檔案中其它的資料專案要求的順序、長度和內容不同,屬性表集合的限制稍微寬鬆一些, 不再要求各個屬性表具有嚴格的順序,並且只要不與已有的屬性名重複, 任何人實現的編譯器都可以向屬性表中寫入自己定義的屬性資訊,Java虛擬機器執行時會忽略掉它不認識的屬性。

位元組碼指令簡介

位元組碼與資料型別

在java虛擬機器的指令集中,大多數的指令都包含了其操作所對應的資料型別資訊, 例如iload指令用於從區域性變數表中載入int型別的資料到運算元棧中,而fload指令載入的則是float型別的資料。 這兩條指令的操作在虛擬機器內部可能是同一段程式碼實現的,但在Class檔案中它們必須擁有各自獨立的操作碼。

大部分的指令都沒有支援整數型別byte、char、short甚至沒有任何指令支援boolean型別。 大多數對於byte、char、short、boolean型別的操作, 實際上都是使用相應的int型別作為運算子型別

載入和儲存指令

載入和儲存指令用於將資料在棧幀中的 區域性變數表 運算元棧 之間來回傳輸。

Java虛擬機器,類檔案結構深度解析

 

運算指令

運算或算術指令用於對運算元棧上的值進行某種特定運算,並把結果重新存入操作棧頂。

大體上算術指令可以分為兩種: 對 整型資料 和對 浮點資料 進行運算指令。 (由於沒有byte、char、short、boolean型別,所以對這類資料的運算應使用int型別指令代替)

型別轉換指令

型別轉換指令可以將兩種不同的數值型別進行相互轉換。 (比如int型別轉換為float型別)  小範圍到大範圍型別安全轉換 ,無需顯式的轉換指令,否則必須顯式的使用轉換指令來完成。

物件建立與訪問指令

雖然類例項和陣列都是物件,但java虛擬機器對類例項和陣列的建立和操作使用了不同的位元組碼指令。

Java虛擬機器,類檔案結構深度解析

 

運算元棧管理指令

如同運算元據結構中的棧一樣,Java虛擬機器也提供了一些用於直接操作運算元棧的指令。

Java虛擬機器,類檔案結構深度解析

 

控制轉移指令

可以認為控制轉移指令就是在有條件或無條件地修改PC暫存器的值。

方法呼叫和返回指令

invokevirtual 指令用於呼叫物件的例項方法
invokeinterface 指令用於呼叫介面方法
invokespecial 指令用於呼叫一些需要特殊處理的例項方法
invokestatic 指令用於呼叫類方法(static方法)
invokedynamic 指令用於在執行時動態解析出呼叫點限定符所使用的方法。

方法呼叫指令與資料型別無關,而方法返回指令是根據返回值的型別區分的。

異常處理指令

在java虛擬機器中,處理異常(catch語句)不是由位元組碼指令來實現的,而是採用 異常表 的方式。

同步指令

java虛擬機器可以支援方法級的同步和方法內部一段指令序列的同步,這兩種同步結構使用管程(Monitor)來支援的。

虛擬機器實現的兩種方式

方式一

將輸入的Java虛擬機器程式碼在載入或執行時翻譯成另外一種虛擬機器的指令集。

方式二

將輸入的Java虛擬機器程式碼在載入或執行時翻譯成宿主主機CPU的本地指令集。(即JIT程式碼生成技術)

Class檔案結構的發展

Class檔案結構已經有十多年曆史了,這10多年間,java技術體系有了翻天覆地的變化,但是Class檔案結構一直處於比較穩定的狀態,Class檔案的主體結構、位元組碼指令的語義和數量幾乎沒有出現過變動, 所有Class檔案格式的改進,都集中在向 訪問標誌 屬性表 這些在設計上就可擴充套件的資料結構中新增內容。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69917606/viewspace-2644321/,如需轉載,請註明出處,否則將追究法律責任。

相關文章