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.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虛擬機運行時會忽略掉它不認識的屬性。