深刻理解JVM(五)Class類的文件結構

1.Class文件的定義

Class文件時一組以8位字節爲基礎單位的二進制流,當遇到須要佔用8位字節以上空間的數據項時,則會按照高位在前的方式分割成若干個*位字節進行存儲。Class文件格式採用一種相似C語言結構體的僞結構來存儲數據,這種僞結構中只有兩種數據類型:無符號數和表。java

1.1.無符號數程序員

無符號數屬於基本的數據類型,根據這些值長度的不一樣分爲:u一、u二、u四、u8,分別表明1字節的無符號數、2字節的無符號數、4字節的無符號數、8字節的無符號數。無符號數能夠用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。數組

1.2.表安全

表示由多個無符號數或者其餘表做爲數據項構成的複合數據類型,全部表都習慣地以"_info結尾"。表用於描述有層次關係的複合結構的數據,整個Class文件本質上就是一張表,它由表6-1所示的數據項構成。函數

2.Class文件的構成

2.1.魔數this

class文件的頭4個字節稱爲魔數,它的惟一做用是肯定這個文件可否爲一個能被虛擬機接受的Class文件。編碼

魔數的做用就至關於文件後綴名,只不事後綴名容易被修改,不安全,所以在class文件中標示文件類型比較合適。cdn

class文件的魔數是用16進製表示的「CAFEBABE」,很是具備浪漫主義色彩,誰說程序員的情商都很低!對象

2.2.版本號blog

緊接着魔數的四個字節是class文件的此版本號和主版本號。 隨着Java的發展, class文件的格式也會作相應的變更。 版本號標誌着class文件在何時, 加入或改變了哪些特性。 舉例來講, 不一樣版本的javac編譯器編譯的class文件, 版本號可能不一樣, 而不一樣版本的JVM能識別的class文件的版本號也可能不一樣, 通常狀況下, 高版本的JVM能識別低版本的javac編譯器編譯的class文件, 而低版本的JVM不能識別高版本的javac編譯器編譯的class文件。 若是使用低版本的JVM執行高版本的class文件,JVM會拋出java.lang.UnsupportedClassVersionError 。

2.3.常量池

2.3.1. 什麼是常量池?

緊接着版本號以後的就是常量池。常量池中存放兩種類型的常量:字面值常量和符號引用。

字面值常量即咱們在程序中定義的字符串、被final修飾的值。

符號引用就是咱們定義的各類名字:類和接口的全限定名、字段的名字 和 描述符、方法的名字 和 描述符。

2.3.2 常量池的特色

常量池長度不固定

常量池的大小是不固定的,所以常量池開頭放置一個u2類型的無符號數,用來存儲當前常量池的容量。JVM根據這個值就知道常量池的頭尾來。

注:這個值是從1開始的,若爲5表示池中有4個常量。

常量池中的常量由而爲表來表示

常量池開頭有個常量池容量計數器,接下來就全是一個個常量了,只不過常量都是由一張張二維表構成,除了記錄常量的值之外,還記錄當前常量的相關信息。

常量池是class文件的資源倉庫

常量池是與本class中其它部分關聯最多的部分

常量池是class文件中空間佔用最大的部分之一

2.3.3常量池中常量的特色

剛纔介紹了,常量池中的常量大致上分爲:字面值常量 和 符號引用。在此基礎上,根據常量的數據類型不一樣,又能夠被細分爲14種常量類型。這14種常量類型都有各自的二維表示結構。每種常量類型的頭1個字節都是tag,用於表示當前常量屬於14種類型中的哪個。

以CONSTANT_Class_info常量爲例,它的二維表示結構以下:

CONSTANT_Class_info表:

類型名稱數量

u1tag1

u2name_index1

tag表示當前常量的類型(當前常量爲CONSTANT_Class_info,所以tag的值應爲7,表示一個類或接口的全限定名);

name_index表示這個類或接口全限定名的位置。它的值表示指向常量池的第幾個常量。它會指向一個CONSTANT_Utf8_info類型的常量,它的二維表結構以下:

CONSTANT_Utf8_info表:

類型名稱數量

u1tag1

u2length1

u1byteslength

CONSTANT_Utf8_info表示字符串常量;

tag表示當前常量的類型,這裏應該是1;

length表示這個字符串的長度;

bytes爲這個字符串的內容(採用縮略的UTF8編碼)

問:爲何Java中定義的類、變量名字必須小於64K?

類、接口、變量等名字都屬於符號引用,它們都存儲在常量池中。而無論哪一種符號引用,它們的名字都由CONSTANT_Utf8_info類型的常量表示,這種類型的常量使用u2存儲字符串的長度。因爲2字節最多能表示65535個數,所以這些名字的最大長度最多隻能是64K。

問:什麼是UTF-8編碼?什麼是縮略UTF-8編碼?

前者每一個字符使用3個字節表示,然後者把128個ASKII碼用1字節表示,某些字符用2字節表示,某些字符用3字節表示。

2.4.訪問標誌

在常量池以後是2字節的訪問標誌。訪問標誌是用來表示這個class文件是類仍是接口、是否被public修飾、是否被abstract修飾、是否被final修飾等。

因爲這些標誌都由是/否表示,所以能夠用0/1表示。

訪問標誌爲2字節,能夠表示16位標誌,但JVM目前只定義了8種,未定義的標誌位一概爲0.

2.5.類索引、父類索引與接口索引集合

類索引(this_class) 和父類索引(super class) 都是一個u2類型的數據,而接口索引(interfaces) 是一組u2類型的數據的集合,Class 文件中由這三項數據來肯定這個類的承關係。類索引用於肯定這個類的全限定名,父類索引用於肯定這個類的父類的全限定名,因爲Java 語言不容許多重繼承,因此父類索引只有一個,除了java.lang.Object 以外,所的Java 類都有父類,所以除了java lang.Object 外,全部Java 類的父類索引都不爲0.接口索引集合就用來描述這個類實現了哪些接口,這些被實現的接口將按implements語句(若是這個類自己是一個接口,則應當是extends語句) 後的接口順序從左到右排列在接口索集合中。

它們按照順序依次排列,類索引和父類索引各自使用一個u2類型的無符號常量,這個常量指向CONSTANT_Class_info類型的常量,該常量的bytes字段記錄了本類、父類的全限定名。

接口索引集合,入口的第一項-u2類型的數據爲接口計數器,表示索引表的容量。若是該類沒有實現任何接口,則該計數器值爲0,後面接口的索引表再也不佔用任何字節。

2.6.字段表集合

字段表用於描述接口或者類中聲明的變量。字段包括類及變量以及實例及變量,但不包括在方法內部聲明的局部變量。

每個字段表只表示一個成員變量,本類中全部的成員變量構成了字段表集合。

2.6.2 字段表結構的定義

類型 名稱 數量

u2 access_flags 1

u2 name_index 1

u2 descriptor_index 1

u2 attributes_count 1

attribute_info attributes attributes_count

access_flags

字段的訪問標誌。在Java中,每一個成員變量都有一系列的修飾符,和上述class文件的訪問標誌的做用同樣,只不過成員變量的訪問標誌與類的訪問標誌稍有區別。

name_index

本字段名字的索引。指向一個CONSTANT_Class_info類型的常量,這裏面存儲了本字段的名字等信息。

descriptor_index

描述符。用於描述本字段在Java中的數據類型等信息(下面詳細介紹)

attributes_count

屬性表集合的長度。

attributes

屬性表集合。到descriptor_index爲止是字段表的固定信息,光有上述信息可能沒法完整地描述一個字段,所以用屬性表集合來存放額外的信息,好比一個字段的值。(下面會詳細介紹)

2.6.3 什麼是描述符

成員變量(包括靜態成員變量和實例變量) 和 方法都有各自的描述符。

對於字段而言,描述符用於描述字段的數據類型;

對於方法而言,描述符用於描述字段的數據類型、參數列表、返回值。

在描述符中,基本數據類型用大寫字母表示,對象類型用「L對象類型的全限定名」表示,數組用「[數組類型的全限定名」表示。

描述方法時,將參數根據上述規則放在()中,()右側按照上述方法放置返回值。並且,參數之間無需任何符號。

2.6.4 字段表須要注意的點

一個class文件的字段表集合中不能出現從父類/接口繼承而來字段;

一個class文件的字段表集合中可能會出現程序猿沒有定義的字段

如編譯器會自動地在內部類的class文件的字段表集合中添加外部類對象的成員變量,供內部類訪問外部類。

Java中只要兩個字段名字相同就沒法經過編譯。但在JVM規範中,容許兩個字段的名字相同但描述符不一樣的狀況,而且認爲它們是兩個不一樣的字段。

Class文件的構成

2.7:方法表的集合

在class文件中,全部的方法以二維表的形式存儲,每張表來表示一個函數,一個類中的全部方法構成方法表的集合。

方法表的結構和字段表的結構一致,只不過訪問標誌和屬性表集合的可選項有所不一樣。

類型名稱數量

u2access_flags1

u2name_index1

u2descriptor_index1

u2attributes_count1

attribute_infoattributesattributes_count

方法表的屬性表集合中有一張Code屬性表,用於存儲當前方法經編譯器編譯事後的字節碼指令。

方法表集合的注意點

若是本class沒有重寫父類的方法,那麼本class文件的方法表集合中是不會出現父類/父接口的方法表;

本class的方法表集合可能出現程序猿沒有定義的方法

編譯器在編譯時會在class文件的方法表集合中加入類構造器和實例構造器。

重載一個方法須要有相同的簡單名稱和不一樣的特徵簽名。JVM的特徵簽名和Java的特徵簽名有所不一樣:

Java特徵簽名:方法參數在常量池中的字段符號引用的集合

JVM特徵簽名:方法參數+返回值

2.8:屬性表的集合

在Class文件,字段表,方法表中均可以攜帶本身的屬性表集合,以用於描述某些場景專有的信息。與Class文件中其它的數據項目要求的順序、長 度和內容不一樣,屬性表集合的限制稍微寬鬆一些,再也不要求各個屬性表具備嚴格的順序,而且只要不與已有的屬性名重複,任何人實現的編譯器均可以向屬性表中寫入本身定義的屬性信息,Java虛擬機運行時會忽略掉它不認識的屬性。

相關文章
相關標籤/搜索