JVM001_类文件结构
無關性的基石
實現語言無關性的基礎是虛擬機和字節碼存儲格式。Java虛擬機不與任何語言綁定(包括Java),它只與‘Class文件’這種特定的二進制文件格式所關聯。Class文件中包含了Java虛擬機指令集、符號表以及其它輔助信息。出于安全考慮,《Java虛擬機規范》要求在Class文件中必須應用許多強制性的語法和結構化約束,但圖靈完備的字節碼格。
Class類文件的結構
Class文件是一組以8個字節為基礎的二進制流,各項數據項目嚴格按照順序緊湊的排列在文件中,中間沒有任務分隔符。當遇到需要占用8個字節以上空間的數據項時,則會按照高位在前的方式分割成若干個8個字節進行存儲。
高位在前: 高位的字節存儲在內存中的低位。
例如:‘123456’ 那么1位高位的字節,6為低位的字節,加入有內存地址 012345,那么0中存1,1中存2.。。。5中存6,這種模式叫做高位在前,也叫大端。
Class文件結構中包括兩種數據結構:‘無符號數’和‘表’。
- 無符號數是基本數據類型。用u1,u2,u3,u4來分別表示1個字節、2個字節、3個字節、4個字節。
- 表是由一系列無符號數或者其他表組成的復合數據結構。所有的表都習慣性的以”_info“結尾。
Class文件格式表: P215
魔數
魔數用來確定該文件是否為能被JVM所接受的Class文件。
現寫兩個Java類
package j1;public class Test {private String item = "itemStr";public String nothing(){return item;} } package j1;public class Test1 {private int count = 1;public int inc() {return count + 1;} }將其編譯后,用notepad++或者sublime打開
可以看到在十六進制下,兩個Class文件的頭四個字節是cafebabe,這四個字節就是魔數。為什么說cafebabe是四個字節呢,因為這里是16進制,1個字節占8位,需要用兩位十六進制符號表示,例如 11001101用十六進制表示應該為0XCD,簡寫為CD.
次版本號
緊跟著魔數后面的兩個字節為次版本號
主版本號
? 緊跟著次版本號后面的為主版本號,0034由16進制轉換為十進制后為52,查詢Class文件版本號可以找到JDK版本為JDK8.所以該文件能被JDK8及以上版本的虛擬機所執行。
常量池
主版本號后為常量池的的入口。常量池的入口放著一個u2類型的數據,用來表示常量池中常量的數目。
0013表示常量池中有18項常量,索引值范圍為1-19。為什么0X0013轉換為十進制后為19,但是這里說常量池中有18項常量呢?因為第一個常量存在索引為1的位置,這么做是考慮到后面某些指向常量池的索引值的數據在特定的情況下需要表達“不引用任何一個常量池項目”的含義,這個時候將索引值設為0即可來表示。
Class文件結構中只有常量池的容量計數是從1開始的,其余都是從0開始。
常量池中主要存放兩大類常量: 字面量和符號引用
字面量
- 文本字符串
- 被final修飾的常量等
符號引用
? 符號引用輸入編譯原理方面的概念
- 被模塊導出或開放的包Packge
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
- 方法句柄和方法類型
- 動態調用點和動態常量
?描述符是什么?方法句柄是什么?動態調用是什么?動態常量是什么
截止JDK13一共有17中不同類型的常量。現總結為如下:
我們以Test1.class文件來分析
紅框內的內容為常量池,其內容按照常量池項目圖來按圖索驥即可。
利用命令javap -v Test1.class或者javap -verbose Test1.class來反編譯字節碼文件,可以得到下圖:
其中Constant pool下項目與16進制文件中紅框內容對應。
訪問標志
常量池結束后,緊接著的2個字節代表訪問標志(access_flags),這個標志用來表示一些類或者接口層次的訪問信息。
比如說這個Class是類還是接口,是否定義為Public類,是否為Abstract類,如果是類的話是否被聲明為final等等。
access_flags一共有16個標志位可用,當前定義了其中的9個。沒有使用到的標志位為0.因此,標志位的值也可以理解為是使用到的標志的位運算或。
圖見: p224
類索引、父類索引、接口索引集合
類索引及父類索引都是一個u2類型的數據,接口索引集合是一組u2類型的數據的集合。這三項數據確定了改類型的繼承及實現關系。
類索引確定了這個類的全限定名,父類索引確定了該類的父類的全限定名。除了java.lang.Object外其余類的父類索引都不為0.接口索引集合用來描述該類實現了哪些接口,這些被實現的接口安裝implements關鍵字(若該類表示的是一個接口,那么應該為extends)后的接口順序從左到右排列在索引集合中。對于接口索引集合,入口的第一項u2類型的數據為接口的計數器,表示索引表的容量,也就是實現了幾個接口,若沒有實現接口,則值為0。可以看到這里有點類似常量池的結構。
字段表集合
圖見: p226-227
字段表用來描述接口或者類中聲明的變量。包括類級變量以及實例級變量,但是不包括方法內部的局部變量。
全限定名: 將類的全限定名中".“用”/"代替后的字符串。
簡單名稱: 沒有類型和參數修飾的方法或者字段。例如有個一方法public void test(int a,int b){},則該方法的簡單名稱為test。一個字段int a = 1;其簡單名稱為”a“。
描述符: 按照一定的規則來描述字段的類型、方法的參數列表(數量,類型及順序)和返回值。
描述符標識字符含義見P227
描述符的一些規則
- 對于數組,每增加一個維度就使用一個前置的“[”來表述,例如int[ ] [ ]對應的描述符為“[[I”。
- 用描述符來描述方法時,按照先參數列表后返回指的順序來描述。例如java.lang.String.toString()的描述符為()Ljava/lang/String。
字段表中可能會有一個屬性集合表,用來存儲一些額外的數據。
字段表集合中不會出現父類或者父接口中繼承而來的字段,但可能出現一些代碼中不存在的字段。對于Class文件格式來講,只要兩個字段的描述符不是完全相同的,那字段重名就是合法的。
方法表集合
結構與字段表集合類似。圖見P229
方法中的Java代碼存在方法屬性表集合中一個名為“Code”的屬性里面。
tip
- 若父類方法沒有在子類中重寫,方法表集合中就不會出現來自父類的方法信息。
- 在編譯時,編譯器可能向方發表自動添加方法,比如類構造器(“()”)、實例構造器("()")
- Java中重載一個方法,需要與原方法具有相同的簡單名稱及與原方法不同的特征簽名。特征簽名是指一個方法中各個參數在常量池中的字段符號引用集合。返回值不包括在特征簽名中。
屬性表集合
這段內容有點煩躁,回頭再看
總結
以上是生活随笔為你收集整理的JVM001_类文件结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Maven配置_01
- 下一篇: c 语言运算符号大全,c语言运算符号详细