title: Class類文件結構
date: 2017-12-16 00:06:52
tags: jvm
categories: jvm
Class類文件結構
無關的基石
Sun 公司發佈了各種平臺上不同的虛擬機版本,這些不同平臺上的虛擬機只識別字節碼文件,這種字節碼文件就是構成平臺無關性的基石.除了平臺無關性,語言無關性也有很大的優勢, Java 虛擬機不和包括 Java 在內的所有語言綁定,只與”Class 文件”這種特定的二進制文件格式所關聯, Class 文件包含了 Java 虛擬機指令和符號表以及若干其他輔助信息.虛擬機不關心 Class 的來源是何種語言.
Class 文件結構
Class 文件採用類似 C 語言結構體的僞結構體來存儲數據,這種僞結構只有兩種數據類型,無符號數和表.
無符號數可以描述數字,索引引用,數量值或者按照 UTF-8編碼構成的字符串值.
表是由無符號數或者其他表作爲數據項構成的複合數據類型
魔數與 Class 文件版本
每個 Class 文件的頭4個字節是魔數,它的唯一作用是確定當前文件能否被虛擬機進行加載.很多文件存儲格式否使用魔數來進行身份識別,使用魔數而不使用擴展名是因爲擴展名隨意更改.
緊接着魔數後的4個字節是版本號,高版本的虛擬機能夠向下兼容以前版本的 Class 文件,但不能運行以後版本的 Class 文件.
常量池
常量池中有14種常量類型,每種類型都有自己的結構.
由於這些常量的個數是不固定的,所以在常量池的入口放置一項 u2類型的數據,代表常量池容量計數值.
虛擬機的常量池主要存放兩大類常量:字面量和符號引用.
字面量接近於 Java 語言層面的常量概念,如字符串,聲明爲 final 的常量值等.
符號引用則數據編譯原理方面的概念,包括:類和結構的全限定名,字段和名稱的描述符,方法的名稱和描述符.
訪問標識
在常量池結束後的兩個字節代表訪問標識,用於識別類或者接口的訪問信息,包括:這個 Class 是類還是接口,是否定義爲 public 類型,是否定義爲 abstract 類型等.
類索引,父類索引與接口索引集合
Class 文件由這三項來確定類的繼承關係,類索引確定這個類的全限定名,父類索引確定這個類父類的全限定名,接口索引集合用來描述這個類實現了那些接口.
字段表集合
字段表用來描述類或接口中聲明的變量,包括類級變量以及實例級變量,但不包括方法內部聲明的局部變量.
方法表集合
方法表的結構如字段一樣,包含了訪問標識,名稱索引,描述符索引幾項.
屬性表集合
- Code 屬性
當代碼經過編譯之後,最終字節碼指令存儲在 Code 屬性內.
- Exceptions 屬性
列舉出方法可能拋出的受查異常
- LineNumberTable 屬性
描述 Java 源代碼行號與字節碼行號之間的關係.
- LocalVarableTable 屬性
描述棧幀中局部變量表中的變量與 Java 源代碼中定義的變量之間的關係.
- SourceFile 屬性
記錄生成這個 Class 文件的源碼文件名稱.
- ConstantValue 屬性
通知虛擬機自動爲靜態變量賦值.
- InnerClasses 屬性
記錄內部類與宿主類之間的關聯.
- Deprecated 及 Synthetic 屬性
這兩個屬性都數據標誌類型的布爾屬性.
Deprecated 用於表示某個類,字段或者方法被程序作者定位不再推薦使用.
Synthetic 代表此字段或者方法並不是由 Java 源碼產生的,而是由編輯器自行添加.
- StackMapTable 屬性
一個複雜的變長屬性,位於 Code 屬性的屬性表中.這個屬性會在虛擬機類加載的字節碼驗證階段被新類型檢查驗證器使用,目的是代替以前比較消耗新能的基於數據流分析的類型推導驗證器.
- Signature 屬性
可以出現於類,屬性表和方法表結構的屬性表中,在 JDK1.5之後,任何類,接口,方法的泛型簽名如果包含了類型變量或參數化類型,則 Signature 屬性會記錄泛型簽名信息.
因爲 Java 語言的泛型採用擦除方法實現,在字節碼中,泛型信息編譯之後統統被擦除掉.
使用擦除的原因是實現簡單,運行期也能節省一些類型所佔的內存空間,壞處是運行期無法像 C# 等有真泛型支持的語言那樣,將泛型類型與用戶定義的類型信息同等對待, Signature 就是爲了彌補這個缺陷,如 Java 的反射 API 能夠獲取泛型類型,就是來源這個屬性.
字節碼指令簡介
由一個字節長度的,代表某種特定操作含義的數據以及後面的零到多個代表此操作所所需參數構成.
由於Java 虛擬機面向操作數棧而不是寄存器及架構,所以大多數的指令都不包含操作數.
字節碼與數據類型
在 Java 虛擬機的指令集中,大多數的指令都包含了其操作所對應的數據類型信息.如: iload 指令用於從局部變量表中加載 int 類型的數據到操作數棧中.
加載與存儲指令
加載和存儲指令用於將數據在棧幀中的局部變量表和操作數棧之間來回傳輸.
如:將一個局部變量加載到操作棧: iload
將一個數值從操作數棧存儲到局部變量表: istore
運算指令
將兩個操作數棧上的數據進行某種特定操作,並將結構重新存入操作棧頂.
類型轉化指令
對兩個數值類型進行相互轉換,一般用於實現用戶代碼中的顯式類型轉換操作.
操作數棧管理指令
操作一個普通操作數據結構中的堆棧一樣, Java 虛擬機提供了一些用於直接操作操作數棧的指令,包括:
- 將操作數棧的棧頂一個或者兩個元素出棧: pop,pop2
- 將棧最頂端的兩個數值互換: swap.
控制轉移指令
可以讓 Java 虛擬機有條件或無條件地從指定位置指令而不是控制轉移指令的下一條指令繼續執行程序.
公有設計和私有設計
Java 虛擬機的實現運行在滿足虛擬機規範的約束下對具體實現做出修改和優化.只要優化後的 Class 文件依然可以被正確讀取,實現者可以選擇任何方式去實現這些語義.
Class 文件結構的發展
自 Class 文件發佈十多年以來, Class 文件的主體結構,字節碼指令的語義和數量幾乎沒有改動,所有的改動都集中在 訪問標識,屬性表這些在設計上可擴展的數據結構中添加內容.
Class 文件格式鎖具備的平臺中立,緊湊,穩定和可擴展的特點,是 Java 技術體系實現平臺無關,語言無關兩項特性的重要支柱.