1. 程式人生 > >深入理解JVM(五):類檔案結構

深入理解JVM(五):類檔案結構

今天我們來介紹一下Class類檔案結構

1.概述

計算機雖然只能識別0和1,但是越來越多的程式語言選擇了與作業系統和機器指令集無關的、平臺中立的格式作為程式編譯後的儲存格式。Java虛擬機器不和包括Java在內的任何語言繫結,只與“class檔案”這種特定的二進位制檔案所關聯,Class檔案中包含了Java虛擬機器指令集合符號表以及若干其他輔助資訊。Java虛擬機器作為一個通用的、機器無關的執行平臺,任何其他語言都可以將其作為語言的產品交付媒介。

2.Class類檔案結構

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

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

Class檔案格式採取一種類似C語言結構體的偽結構體來儲存數,這種偽結構有兩種資料型別:無符號數和表

表是由多個無符號數和其他表作為資料項構成複合資料型別,所有表都習慣性地以"_info"結尾。表用於描述有層次關係的複合結構的資料,整個Class檔案本質就是一張表。它由下表構成。
在這裡插入圖片描述

2.1 魔數與Class檔案的版本

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

2.2 常量池

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

  1. 類和介面的全限定名
  2. 欄位的名稱和描述符
  3. 方法的名稱和描述符

2.3 訪問標誌

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

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

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

2.5 欄位表集合

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

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

欄位的作用域(public ,private,protected修飾符),是例項變數還是類變數(static修飾符)、可變性(final)、併發可見性(volatile修飾符,是否強制從主記憶體讀寫)、可否被序列化(transient修飾符)、欄位資料型別、欄位名稱。上述這些資訊中,各個修飾符都是布林值,要麼有某個修飾符,要麼沒有,很適合使用標誌位來表示。而欄位叫什麼名字、欄位被定義為什麼資料型別這些都是無法固定的,只能引用常量池中常量來描述。

2.6 方法表集合

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

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

2.7 屬性表集合

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

屬性名稱都是對應常量池中存放的字元。

3.位元組碼指令簡介

在java虛擬機器的指令集中,大多數的指令都包含了其操作對應的資料型別資訊,例如iload指令用於從區域性變量表中載入int型別的資料到運算元棧中,而fload指令載入的則是float型別的資料。

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

3.1 載入和儲存指令

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

3.2 運算指令

運算或者算術指令用於對運算元棧上的值進行某種特定的運算,並把結果重新存入操作棧頂。入操作棧頂。 大體上算術指令可以分為兩種:對整型資料和對浮點資料進行運算指令。(由於沒有byte、char、short、boolean型別,所以對這類資料的運算應使用int型別指令代替)

3.3 型別轉換指令

型別轉換指令可以將兩種不同的數值型別進行相互轉換。(比如int類轉換成float型別)

3.4 物件建立和訪問指令

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

3.5 運算元棧管理指令

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

3.6 控制轉移指令

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

3.7 方法呼叫和返回指令

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

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

3.8 異常處理指令

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

3.9 同步指令

java虛擬機器可以支援方法級的同步和方法內部一段指令序列的同步,這兩種同步結構試驗觀城來支援的。