要點提煉| 理解JVM之類檔案結構

釐米姑娘發表於2019-01-02

之前還在美團實習的時候,當時讀《深入理解Java虛擬機器》由於時間原因只總結了幾個章節,現在把餘下的幾個章節補充上,發表順序有些混亂,章節主線詳見文章彙總|學習Android的一點一滴

本篇將介紹Class檔案結構中的各個組成部分,以及每個部分的定義、資料結構和使用,有利於進一步瞭解虛擬機器執行引擎。

  • 概述
  • 類檔案結構
  • 位元組碼指令

1.概述

執行在各種不同平臺上的虛擬機器通過載入和執行同一種平臺無關的位元組碼來實現了程式的“一次編寫,到處執行”。可見位元組碼是構成平臺無關性的基石。

Java虛擬機器不和Java等任何語言繫結,只和儲存位元組碼的Class檔案這種特定的二進位制檔案格式關聯,且並不關心Class的來源是何種語言,也體現了Java虛擬機器的語言無關性

要點提煉| 理解JVM之類檔案結構


2.類檔案結構

  • Class檔案是一組以8位位元組為基礎單位的二進位制流,各個資料專案嚴格按照順序緊湊地排列在Class檔案之中,中間無任何分隔符,當遇到需要佔用8位位元組以上空間的資料項時,會按照高位在前的方式分割成若干個8位位元組進行儲存。
  • Class檔案格式採用一種類似於C語言結構體的偽結構來儲存資料,包含兩種資料型別:
    • 無符號數:屬於基本資料型別;以u1、u2、u4、u8來分別代表1個位元組、2個位元組、4個位元組和8個位元組的無符號數;可用於描述數字、索引引用、數量值或按照UTF-8 編碼構成的字串值。
    • :由多個無符號數或其他表作為資料項構成的複合資料型別;常以“_info”結尾;可用於描述有層次關係的複合結構的資料。
  • 整個Class檔案本質上就是一張表,所包含的資料項如圖:

要點提煉| 理解JVM之類檔案結構

接下來依次介紹表中各個資料項的具體含義。

a. 魔數

  • 魔數(Magic Number):每個Class檔案的頭4個位元組
  • 作用:判斷該檔案是否為一個能被虛擬機器接受的Class檔案

b.版本號

  • 版本號:包含主版本號和一系列次版本號
    • 次版本號(Minor Version):第5和第6個位元組
    • 主版本號(Major Version):第7和第8個位元組
  • 作用:判斷該檔案是否在虛擬機器處理的有效範圍內

c.常量池

  • 常量池:使用一個前置的容量計數器(constant_pool_count)加上若干個連續的常量項(constant_pool)來描述
    • 容量計數器:從1開始,目的是滿足後面某些指向常量池的索引值的資料在特定情況下需要表達“不引用任何一個常量池專案”的含義,這時可以把索引值置為0來表示
    • 常量項:如constant_pool_count=2表示常量池中有1個常量項
  • 特點:是Class檔案的資源倉庫、是Class檔案結構中與其他專案關聯最多的資料型別、是佔用Class檔案空間最大的資料專案之一、是在Class檔案中第一個出現的表型別資料專案
  • 存放內容:兩大類常量
    • 字面量(Literal):指Java語言層面的常量概念,如文字字串、宣告為final的常量值等
    • 符號引用(Symbolic References):指編譯原理方面的概念,包含類和介面的全限定名(Fully Qualified Name)、欄位的名稱和描述符(Descriptor)、方法的名稱和描述符

Java程式碼進行Javac編譯的過程同虛擬機器載入Class檔案的過程是動態連線的,因此在Class檔案中不會儲存各個方法、欄位的最終記憶體佈局資訊,這就需要虛擬機器在執行時從常量池獲得對應的符號引用,再在類建立時或執行時解析、翻譯到具體的記憶體地址之中。

d.訪問標誌

  • 訪問標誌(access_flags):常量池結束後兩個位元組
  • 作用:識別一些類或者介面層次的訪問資訊,包括該Class是類還是介面、是否定義為public型別、是否定義為abstract型別、若是類是否被宣告為final等。具體的標誌位以及含義見圖:

要點提煉| 理解JVM之類檔案結構

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

  • 類索引(this_class)和父類索引(super_class)都是一個u2型別的資料、介面索引集合(interfaces)是一組u2型別的資料的集合
  • 作用: 通過這三項資料來確定這個類的繼承關係,具體的
    • 類索引:確定這個類的全限定名
    • 父類索引:確定這個類的父類的全限定名
    • 介面索引集合:描述這個類所實現的介面,並按照implements語句後的介面順序從左到右排列在介面索引集合中
      • 介面索引集合的入口第一項u2型別資料為介面計數器(interfaces_count),從0計數,如nterfaces_count=2表示該類實現了兩個介面

類全限定名:把類全名中的“.”都替換成“/”,為了使連續的多個全限定名之間不產生混淆,在使用時最後一般會加入一個“;”表示全限定名結束

f.欄位表集合

  • 欄位表(field_info):用於描述介面或者類中宣告的變數
  • 格式如圖
    • 要點提煉| 理解JVM之類檔案結構
    • access_flags:存放欄位的修飾符,具體的標誌位以及含義見圖:
    • 要點提煉| 理解JVM之類檔案結構
    • name_index:存放欄位的簡單名稱,即沒有型別和引數修飾的欄位名稱
    • descriptor_index:存放欄位和方法的描述符,包括欄位的資料型別、方法的引數列表(包括數量、型別以及順序)和返回值,具體的標誌位以及含義見圖:
    • 要點提煉| 理解JVM之類檔案結構
    • attribute_info:屬性表見後

g.方法表集合

  • 方法表(methods_info):用於描述介面或者類中宣告的方法
  • 格式如圖,可見和描述欄位的方式非常類似,僅在訪問標誌和屬性表集合的可選項中有所區別。
    • 要點提煉| 理解JVM之類檔案結構
    • 要點提煉| 理解JVM之類檔案結構

h.屬性表集合

  • 屬性表(attribute_info):用於描述某些場景專有的資訊,在欄位表、方法表等都攜帶自己的屬性表集合
  • 種類:
    要點提煉| 理解JVM之類檔案結構
  • 結構:屬性名需要從常量池中引用一個CONSTANT_Utf8_info型別的常量來表示,屬性值是自定義的、需要通過一個u4的長度屬性說明屬性值所佔用的位數
    要點提煉| 理解JVM之類檔案結構

舉例class檔案結構解析class檔案屬性表解析


3.位元組碼指令

  • 構成:由一個位元組長度的表示某種特定操作含義的操作(操作碼、Opcode)和零至多個代表此操作所需的引數(運算元、Operands)構成
  • 特點:非完全獨立,即並非每種資料型別和每一種操作都有對應的指令,有些單獨的指令可以在必要的時候用來將一些不支援的型別轉換為可被支援的型別
  • 分類:將位元組碼操作按用途大致分為9類
    • 載入和儲存指令:用於將資料在棧幀中的區域性變數表和運算元棧之間來回傳輸
    • 運算指令:用於對兩個運算元棧上的值進行某種特定運算,並把結果重新存入到操作棧頂
    • 型別轉換指令 :用於實現使用者程式碼中的顯式型別轉換操作,或者用於處理位元組碼指令集中資料型別相關指令無法與資料型別一一對應的問題
    • 物件建立與訪問指令:用於物件建立,並通過物件訪問指令獲取物件例項或者陣列例項中的欄位或者陣列元素
    • 運算元棧管理指令:用於直接操作運算元棧
    • 控制轉移指令:用於從指定的位置有條件或無條件地進行指令
    • 方法呼叫和返回指令:用於方法的呼叫,並根據返回值的型別去返回
    • 異常處理指令:用於檢測到異常狀況時自動丟擲異常
    • 同步指令:用於方法內部一段指令序列的同步

具體指令見Java虛擬機器位元組碼指令簡介


相關文章