Java虛擬機不和包括Java在內的任何語言綁定,只與 "Class文件" 這種特定的二進制文件所關聯, Class文件中包含了Java虛擬機指令集合符號表以及若干其它輔助信息。 Java虛擬機做爲一個通用的、機器無關的執行平臺,任何其餘語言均可以將其做爲語言的產品交付媒介。java
Class文件是一組以8位字節爲基礎的二進制流, 各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符, 這使得整個Class文件中存儲的內容幾乎所有是程序運行的必要數據,沒有空隙存在。 當遇到須要佔用8位字節以上空間的數據項時,則會按照高位在前的方式分割成若干個8位字節進行存儲。數組
Class文件格式採用一種相似於C語言結構體的僞結構來存儲數,這種僞結構有兩種數據類型:安全
無符號數數據結構
表併發
無符號數:屬於基本數據類型,以u一、u二、u四、u8來表明1個字節、2個字節、4個字節、8個字節的無符號數, 無符號數能夠用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。編碼
表:由多個無符號數或者其餘表做爲數據項構成的複合數據類型,全部表都習慣性地以"_info"結尾。 表用於描述有層次關係的複合結構的數據,整個Class文件本質上就是一張表。spa
每一個Class文件的頭四個字節稱爲魔數(Magic Number), 它的惟一做用是肯定這個文件是否爲一個能被虛擬機接收的Class文件。 緊接着魔數的四個字節存儲的是Class文件的版本號: 第五和第六是次版本號,第七和第八是主版本號。翻譯
緊接着主次版本號以後的是常量池入口,常量池能夠理解爲Class文件之中的資源倉庫, 它是Class文件結構中與其餘項目關聯最多的數據類型,也是佔用Class文件空間最大的數據項目之一, 同時它仍是在Class文件中第一個出現的表類型數據項目。 常量池主要存放兩大常量:字面量和符號引用。 字面量比較接近於java語言層面的的常量概念,如文本字符串、聲明爲final的常量值等。 而符號引用則屬於編譯原理方面的概念。包括下面三類常量:設計
類和接口的全限定名對象
字段的名稱和描述符
方法的名稱和描述符
因爲常量池中常量的數量是不固定的,因此在常量池的入口須要設置一項u2類型的數據,表明常量池容量計數值。 與Java中語言習慣不同的是,這個容量技術是從1而不是0開始的,以下圖表示,常量池容量(偏移地址:0x00000008)
爲十六進制數0x0016,即十進制22,這表明常量池中有21項常量,索引值範圍爲1~21。在Class文件格式規範制定之時,設計者將第0項常量空出來是有特殊考慮的,這樣作的目的在於 知足後面某些指向常量池的索引值的數據在特定狀況下須要表達
"不引用任何一個常量池項目"的含義,這種狀況就能夠把索引值置爲0來表示。 Class文件結構中只有常量池的容量計數從1開始, 對於其餘集合類型,包括接口索引結合,字段表集合,方法表集合等容量技術都與通常習慣相同,從0開始。
在常量池結束以後,緊接着的兩個字節表明訪問標誌, 這個標誌用於識別一些類或者接口層次的訪問信息, 包括:這個Class是類仍是接口,是否爲public或者abstract類型,若是是類的話是否聲明爲final等等。 標誌位和標誌的含義對應以下:
類索引、父類索引與接口索引集合都按順序排列在訪問標誌以後, Class文件由這三項數據來肯定這個類的繼承關係。 類索引用於肯定這個類的全限定名, 父類索引用於肯定這個類的父類的全限定名,因爲java語言的單繼承,因此父類索引只有一個, 除了java.lang.Object以外,全部的java類都有父類,所以除了java.lang.Object外,全部java類的父類索引都不爲0。 接口索引集合用來描述這個類實現了哪些接口,這些被實現的接口將按 implents(若是這個類自己是接口的話則是extends)後的接口順序從左到右排列在 接口索引集合中。
字段表(field info)用於描述接口或類中聲明的變量。 字段包括類變量和實例變量,但不包括在方法內部聲明的局部變量。
咱們能夠想想在Java中描述一個字段能夠包含什麼信息呢?
字段的做用域(public ,private,protected修飾符),是實例變量仍是類變量(static修飾符)、 可變性(final)、併發可見性(volatile修飾符,是否強制從主內存讀寫)、能否被序列化(transient修飾符)、 字段數據類型、字段名稱。
上述這些信息中,各個修飾符都是布爾值,要麼有某個修飾符,要麼沒有,很適合使用標誌位來表示。 而字段叫什麼名字、字段被定義爲何數據類型這些都是沒法固定的,只能引用常量池中常量來描述。
Class文件存儲格式中對方法的描述與對字段的描述幾乎採用了徹底一致的方式。 方法表的結構如同字段表同樣,依次包括了訪問標誌、名稱索引、描述符索引、屬性表集合幾項。
在Class文件,字段表,方法表中均可以攜帶本身的屬性表集合,以用於描述某些場景專有的信息。 與Class文件中其它的數據項目要求的順序、長度和內容不一樣,屬性表集合的限制稍微寬鬆一些, 再也不要求各個屬性表具備嚴格的順序,而且只要不與已有的屬性名重複, 任何人實現的編譯器均可以向屬性表中寫入本身定義的屬性信息,Java虛擬機運行時會忽略掉它不認識的屬性。
在java虛擬機的指令集中,大多數的指令都包含了其操做所對應的數據類型信息, 例如iload指令用於從局部變量表中加載int類型的數據到操做數棧中,而fload指令加載的則是float類型的數據。 這兩條指令的操做在虛擬機內部多是同一段代碼實現的,但在Class文件中它們必須擁有各自獨立的操做碼。
大部分的指令都沒有支持整數類型byte、char、short甚至沒有任何指令支持boolean類型。 大多數對於byte、char、short、boolean類型的操做,實際上都是使用相應的int類型做爲運算符類型。
加載和存儲指令用於將數據在棧幀中的局部變量表和操做數棧之間來回傳輸。
運算或算術指令用於對操做數棧上的值進行某種特定運算,並把結果從新存入操做棧頂。
大致上算術指令能夠分爲兩種: 對整型數據和對浮點數據進行運算指令。 (因爲沒有byte、char、short、boolean類型,因此對這類數據的運算應使用int類型指令代替)
類型轉換指令能夠將兩種不一樣的數值類型進行相互轉換。 (好比int類型轉換爲float類型) 小範圍到大範圍類型安全轉換,無需顯式的轉換指令,不然必須顯式的使用轉換指令來完成。
雖然類實例和數組都是對象,但java虛擬機對類實例和數組的建立和操做使用了不一樣的字節碼指令。
如同操做數據結構中的棧同樣,Java虛擬機也提供了一些用於直接操做操做數棧的指令。
能夠認爲控制轉移指令就是在有條件或無條件地修改PC寄存器的值。
invokevirtual 指令用於調用對象的實例方法 invokeinterface 指令用於調用接口方法 invokespecial 指令用於調用一些須要特殊處理的實例方法 invokestatic 指令用於調用類方法(static方法) invokedynamic 指令用於在運行時動態解析出調用點限定符所使用的方法。
方法調用指令與數據類型無關,而方法返回指令是根據返回值的類型區分的。
在java虛擬機中,處理異常(catch語句)不是由字節碼指令來實現的,而是採用異常表的方式。
java虛擬機能夠支持方法級的同步和方法內部一段指令序列的同步,這兩種同步結構使用管程(Monitor)來支持的。
將輸入的Java虛擬機代碼在加載或執行時翻譯成另一種虛擬機的指令集。
將輸入的Java虛擬機代碼在加載或執行時翻譯成宿主主機CPU的本地指令集。(即JIT代碼生成技術)
Class文件結構已經有十多年曆史了,這10多年間,java技術體系有了翻天覆地的變化,可是Class文件結構一直處於比較穩定的狀態,Class文件的主體結構、字節碼指令的語義和數量幾乎沒有出現過變更, 全部Class文件格式的改進,都集中在向訪問標誌、屬性表這些在設計上就可擴展的數據結構中添加內容。