類檔案結構——深入理解Java虛擬機器 筆記三

小新而已發表於2020-05-19

在之前的筆記中記錄過,Java程式變成可執行檔案的步驟是:原始碼——>經過編譯變成class檔案——>經過JVM虛擬機器變成可執行的二進位制檔案。因此,為了對JVM執行程式的過程有一個好的瞭解,我們需要先明白class檔案到底是什麼東西,它裡面有那些資訊以及如何儲存的。

Class類檔案結構

   Class檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊排列在Class檔案中。當遇到需要佔用超過8位位元組的資料項時,則會按照高位在前的方式分割成若干個8位位元組進行儲存。
   Class檔案格式採用一種類似於C語言結構體的偽結構來儲存,這種偽結構中只有兩種資料型別:無符號數和表。
   無符號數是基本的資料型別,以u1,u2,u4,u8分別表示1個位元組、2個位元組、4個位元組、8個位元組的無符號數,無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成的字串值。
   表是由多個無符號數或者其他表作為資料項構成的複合資料結構,所有表習慣性地以_info結尾。
   我們先來看一下Class檔案的格式,然後對這些資料一一進行講解。

魔數和Class檔案的版本

   每個Class檔案的頭四個位元組稱為魔數,作用是來表示該檔案是一個可以被虛擬機器接受的Class檔案。其作用類似於副檔名,但副檔名易更改,而魔數來進行型別判別比較安全。
   緊接著的四個位元組儲存的是Class檔案的版本號。第5、6位元組為次版本號,第7、8位元組為主版本號。

常量池

   版本號之後的就是常量池。常量池是Class檔案結構中與其他專案關聯最多的資料型別,也是佔用Class檔案空間最大的資料專案之一。
   常量池中主要存放兩大類常量,字面量和符號引用。字面量比較接近於Java語言的常量概念,比如文字字串、被宣告為final的常量值等等而符號引用屬於編譯原理方面的概念,包括了下面三類常量:1、類和介面的全限定名 2、欄位的名稱和描述符 3、方法的名稱和描述符
   Java程式碼進行編譯的時候,沒有C那樣用"連結"這一步驟,而是在虛擬機器載入Class檔案的時候進行動態連結。也就是Class檔案不會儲存各個方法和欄位的最終記憶體佈局資訊,因此這些欄位和方法的符號引用不經過轉換的話是無法被虛擬機器使用的。當虛擬機器執行的時候,需要從常量池獲得對應的符號引用,再在類建立或執行的時候解析並翻譯到具體記憶體地址中 ??

   對這些一一列舉也沒有什麼意義,如果需要某一項的內容,可以單獨查詢。本章我認為更關鍵的是對Class檔案怎麼生成的,怎麼運作的理解。
   在常量池中,記載了各種常量(包括系統執行時內部需要的)、方法表(用來記錄該類的所有方法)、屬性表等。他們的運作方法大同小異:通過已經定義的常量(包括字串常量、方法名之類的資訊)來填充對應的表資訊,比如方法表中,我們需要得到方法名、返回值、引數等等資訊,這些資訊都儲存在常量池中,只需要引用到對應位置即可表示。而方法表中更為關鍵的資訊就是Code屬性了,Code屬性表示了該方法的執行過程。
   先看一下Code屬性表的內容:

   裡面像attribute_name_index之類都是索引,指向常量池中的一個字串來表示對應的資料常量。關鍵的地方是code_length和後面的code,他們用來儲存Java原始碼編譯後生成的位元組碼指令。code_length程式碼位元組碼長度,code用於儲存位元組碼指令的一系列位元組流,每一個指令都是一個u1型別的單位元組。,當虛擬機器讀到對應位元組碼的位元組後,他會查詢出是什麼指令並執行。
   
   通過指令的分析,我們可以看到,通過位元組碼我們可以實現利用常量池中的資料資訊,來分析執行結果並執行的。
   在這個位元組碼中,它執行的資料交換、方法呼叫都是基於操作棧進行的,比如2A將物件移動到棧頂,B7是以棧頂物件為接受者,他們都是對一個棧進行的操作,但又有像invokespecial這樣帶引數的指令,這就與單純使用堆疊進行操作不相符,因此另一個問題是JVM如何堆位元組碼執行。

   
   直接使用javap -verbose 類的class檔案 即可得到該class的內容。

相關文章