之前還在美團實習的時候,當時讀《深入理解Java虛擬機》由于時間原因只總結了幾個章節,現在把余下的幾個章節補充上,發表順序有些混亂,章節主線詳見文章匯總|學習Android的一點一滴。
本篇將介紹Class文件結構中的各個組成部分,以及每個部分的定義、數據結構和使用,有利于進一步了解虛擬機執行引擎。
- 概述
- 類文件結構
- 字節碼指令
1.概述
運行在各種不同平臺上的虛擬機通過載入和執行同一種平臺無關的字節碼來實現了程序的“一次編寫,到處運行”。可見字節碼是構成平臺無關性的基石。
Java虛擬機不和Java等任何語言綁定,只和存儲字節碼的Class文件這種特定的二進制文件格式關聯,且并不關心Class的來源是何種語言,也體現了Java虛擬機的語言無關性。
2.類文件結構
- Class文件是一組以8位字節為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間無任何分隔符,當遇到需要占用8位字節以上空間的數據項時,會按照高位在前的方式分割成若干個8位字節進行存儲。
- Class文件格式采用一種類似于C語言結構體的偽結構來存儲數據,包含兩種數據類型:
- 無符號數:屬于基本數據類型;以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節和8個字節的無符號數;可用于描述數字、索引引用、數量值或按照UTF-8 編碼構成的字符串值。
- 表:由多個無符號數或其他表作為數據項構成的復合數據類型;常以“_info”結尾;可用于描述有層次關系的復合結構的數據。
- 整個Class文件本質上就是一張表,所包含的數據項如圖:
接下來依次介紹表中各個數據項的具體含義。
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文件中不會保存各個方法、字段的最終內存布局信息,這就需要虛擬機在運行時從常量池獲得對應的符號引用,再在類創建時或運行時解析、翻譯到具體的內存地址之中。
- 常量池中每一個常量都是一個表,詳解見Class文件結構--常量池(一)
d.訪問標志
- 訪問標志(access_flags):常量池結束后兩個字節
- 作用:識別一些類或者接口層次的訪問信息,包括該Class是類還是接口、是否定義為public類型、是否定義為abstract類型、若是類是否被聲明為final等。具體的標志位以及含義見圖:
e.類索引、父類索引與接口索引集合
- 類索引(this_class)和父類索引(super_class)都是一個u2類型的數據、接口索引集合(interfaces)是一組u2類型的數據的集合
- 作用: 通過這三項數據來確定這個類的繼承關系,具體的
- 類索引:確定這個類的全限定名
- 父類索引:確定這個類的父類的全限定名
- 接口索引集合:描述這個類所實現的接口,并按照implements語句后的接口順序從左到右排列在接口索引集合中
- 接口索引集合的入口第一項u2類型數據為接口計數器(interfaces_count),從0計數,如nterfaces_count=2表示該類實現了兩個接口
類全限定名:把類全名中的“.”都替換成“/”,為了使連續的多個全限定名之間不產生混淆,在使用時最后一般會加入一個“;”表示全限定名結束
f.字段表集合
- 字段表(field_info):用于描述接口或者類中聲明的變量
- 格式如圖
- access_flags:存放字段的修飾符,具體的標志位以及含義見圖:
- name_index:存放字段的簡單名稱,即沒有類型和參數修飾的字段名稱
- descriptor_index:存放字段和方法的描述符,包括字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值,具體的標志位以及含義見圖:
- attribute_info:屬性表見后
g.方法表集合
- 方法表(methods_info):用于描述接口或者類中聲明的方法
- 格式如圖,可見和描述字段的方式非常類似,僅在訪問標志和屬性表集合的可選項中有所區別。
h.屬性表集合
- 屬性表(attribute_info):用于描述某些場景專有的信息,在字段表、方法表等都攜帶自己的屬性表集合
-
種類:
- 結構:屬性名需要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,屬性值是自定義的、需要通過一個u4的長度屬性說明屬性值所占用的位數
3.字節碼指令
- 構成:由一個字節長度的表示某種特定操作含義的操作(操作碼、Opcode)和零至多個代表此操作所需的參數(操作數、Operands)構成
- 特點:非完全獨立,即并非每種數據類型和每一種操作都有對應的指令,有些單獨的指令可以在必要的時候用來將一些不支持的類型轉換為可被支持的類型
- 分類:將字節碼操作按用途大致分為9類
- 加載和存儲指令:用于將數據在棧幀中的局部變量表和操作數棧之間來回傳輸
- 運算指令:用于對兩個操作數棧上的值進行某種特定運算,并把結果重新存入到操作棧頂
- 類型轉換指令 :用于實現用戶代碼中的顯式類型轉換操作,或者用于處理字節碼指令集中數據類型相關指令無法與數據類型一一對應的問題
- 對象創建與訪問指令:用于對象創建,并通過對象訪問指令獲取對象實例或者數組實例中的字段或者數組元素
- 操作數棧管理指令:用于直接操作操作數棧
- 控制轉移指令:用于從指定的位置有條件或無條件地進行指令
- 方法調用和返回指令:用于方法的調用,并根據返回值的類型去返回
- 異常處理指令:用于檢測到異常狀況時自動拋出異常
- 同步指令:用于方法內部一段指令序列的同步
具體指令見Java虛擬機字節碼指令簡介