各類不一樣平臺的虛擬機與全部平臺都統一使用的程序存儲格式——字節碼(ByteCode)是java
構成平臺無關性的基石,也是語言無關性的基礎。Java 虛擬機不和包括 Java 在內的任何數組
語言綁定,它只與「Class 文件」這種特定的二進制文件格式所關聯,Class 文件中包含了安全
Java 虛擬機指令集和符號表以及若干其餘輔助信息。數據結構
任何一個 Class 文件都對應着惟一一個類或接口的定義信息,但反過來講,Class 文件實ide
際上它並不必定以磁盤文件的形式存在。編碼
Class 文件是一組以 8 位字節爲基礎單位的二進制流。spa
各個數據項目嚴格按照順序緊湊地排列在 Class 文件之中,中間沒有添加任何分隔符,這code
使得整個 Class 文件中存儲的內容幾乎所有是程序運行的必要數據,沒有空隙存在。對象
Class 文件格式採用一種相似於 C 語言結構體的僞結構來存儲數據,這種僞結構中只有兩blog
種數據類型:無符號數和表。
無符號數屬於基本的數據類型,以 u一、u二、u四、u8 來分別表明 1 個字節、2 個字節、4
個字節和 8 個字節的無符號數,無符號數能夠用來描述數字、索引引用、數量值或者按照
UTF-8 編碼構成字符串值。
表是由多個無符號數或者其餘表做爲數據項構成的複合數據類型,全部表都習慣性地以
「_info」結尾。表用於描述有層次關係的複合結構的數據,整個 Class 文件本質上就是一
張表。
Class 的結構不像 XML 等描述語言,因爲它沒有任何分隔符號,因此在其中的數據項,無
論是順序仍是數量,都是被嚴格限定的,哪一個字節表明什麼含義,長度是多少,前後順序
如何,都不容許改變。
按順序包括:
魔數與 Class 文件的版本
每一個 Class 文件的頭 4 個字節稱爲魔數(Magic Number),它的惟一做用是肯定這個文件
是否爲一個能被虛擬機接受的 Class 文件。使用魔數而不是擴展名來進行識別主要是基於
安全方面的考慮,由於文件擴展名能夠隨意地改動。文件格式的制定者能夠自由地選擇魔
數值,只要這個魔數值尚未被普遍採用過同時又不會引發混淆便可。
緊接着魔數的 4 個字節存儲的是 Class 文件的版本號:第 5 和第 6 個字節是次版本號
(MinorVersion),第 7 和第 8 個字節是主版本號(Major Version)。Java 的版本號是從
45 開始的,JDK 1.1 以後的每一個 JDK 大版本發佈主版本號向上加 1 高版本的 JDK 能向下
兼容之前版本的 Class 文件,但不能運行之後版本的 Class 文件,即便文件格式並未發生
任何變化,虛擬機也必須拒絕執行超過其版本號的 Class 文件。
常量池
常量池中常量的數量是不固定的,因此在常量池的入口須要放置一項 u2 類型的數據,代
表常量池容量計數值(constant_pool_count)。與 Java 中語言習慣不同的是,這個容
量計數是從 1 而不是 0 開始的
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。
字面量比較接近於 Java 語言層面的常量概念,如文本字符串、聲明爲 final 的常量值等。
而符號引用則屬於編譯原理方面的概念,包括了下面三類常量:
類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方
法的名稱和描述符
訪問標誌
用於識別一些類或者接口層次的訪問信息,包括:這個 Class 是類仍是接口;是否認義爲
public 類型;是否認義爲 abstract 類型;若是是類的話,是否被聲明爲 final 等
類索引、父類索引與接口索引集合
這三項數據來肯定這個類的繼承關係。類索引用於肯定這個類的全限定名,父類索引用於
肯定這個類的父類的全限定名。因爲 Java 語言不容許多重繼承,因此父類索引只有一
個,除了 java.lang.Object 以外,全部的 Java 類都有父類,所以除了 java.lang.Object
外,全部 Java 類的父類索引都不爲 0。接口索引集合就用來描述這個類實現了哪些接口,
這些被實現的接口將按 implements 語句(若是這個類自己是一個接口,則應當是 extends
語句)後的接口順序從左到右排列在接口索引集合中
字段表集合
描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量。
而字段叫什麼名字、字段被定義爲何數據類型,這些都是沒法固定的,只能引用常量池
中的常量來描述。
字段表集合中不會列出從超類或者父接口中繼承而來的字段,但有可能列出本來 Java 代
碼之中不存在的字段,譬如在內部類中爲了保持對外部類的訪問性,會自動添加指向外部
類實例的字段。
方法表集合
描述了方法的定義,可是方法裏的 Java 代碼,通過編譯器編譯成字節碼指令後,存放在
屬性表集合中的方法屬性表集合中一個名爲「Code」的屬性裏面。
與字段表集合相相似的,若是父類方法在子類中沒有被重寫(Override),方法表集合中
就不會出現來自父類的方法信息。但一樣的,有可能會出現由編譯器自動添加的方法,最
典型的即是類構造器「<clinit>」方法和實例構造器「<init>」
屬性表集合
存儲 Class 文件、字段表、方法表都本身的屬性表集合,以用於描述某些場景
專有的信息。如方法的代碼就存儲在 Code 屬性表中。
Java 虛擬機的指令由一個字節長度的、表明着某種特定操做含義的數字(稱爲操做碼,
Opcode)以及跟隨其後的零至多個表明此操做所需參數(稱爲操做數,Operands)而構
成。因爲限制了 Java 虛擬機操做碼的長度爲一個字節(即 0~255),這意味着指令集的操做
碼總數不可能超過 256 條。
大多數的指令都包含了其操做所對應的數據類型信息。例如:
iload 指令用於從局部變量表中加載 int 型的數據到操做數棧中,而 fload 指令加載的則是
float 類型的數據。
大部分的指令都沒有支持整數類型 byte、char 和 short,甚至沒有任何指令支持 boolean
類型。大多數對於 boolean、byte、short 和 char 類型數據的操做,實際上都是使用相應
的 int 類型做爲運算類型
閱讀字節碼做爲了解 Java 虛擬機的基礎技能,請熟練掌握。請熟悉並掌握常見指令即
可。
用於將數據在棧幀中的局部變量表和操做數棧之間來回傳輸,這類指令包括以下內容。
將一個局部變量加載到操做棧:iload、iload_<n>、lload、lload_<n>、fload、fload_
<n>、dload、dload_<n>、aload、aload_<n>。
將一個數值從操做數棧存儲到局部變量表:istore、istore_<n>、lstore、lstore_<n>、
fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>。
將一個常量加載到操做數棧:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、
iconst_m一、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>。
擴充局部變量表的訪問索引的指令:wide。
用於對兩個操做數棧上的值進行某種特定運算,並把結果從新存入到操做棧頂。
加法指令:iadd、ladd、fadd、dadd。
減法指令:isub、lsub、fsub、dsub。
乘法指令:imul、lmul、fmul、dmul 等等
能夠將兩種不一樣的數值類型進行相互轉換,
Java 虛擬機直接支持如下數值類型的寬化類型轉換(即小範圍類型向大範圍類型的安全轉
換):
int 類型到 long、float 或者 double 類型。
long 類型到 float、double 類型。
float 類型到 double 類型。
處理窄化類型轉換(Narrowing Numeric Conversions)時,必須顯式地使用轉換指令來完
成,這些轉換指令包括:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l 和 d2f。
new。
newarray、anewarray、multianewarray。
getfield、putfield、getstatic、putstatic。
把一個數組元素加載到操做數棧的指令:baload、caload、saload、iaload、laload、
faload、daload、aaload。
將一個操做數棧的值存儲到數組元素中的指令:bastore、castore、sastore、iastore、
fastore、dastore、aastore。
取數組長度的指令:arraylength。
instanceof、checkcast。
如同操做一個普通數據結構中的堆棧那樣,Java 虛擬機提供了一些用於直接操做操做數棧
的指令,包括:將操做數棧的棧頂一個或兩個元素出棧:pop、pop2。
複製棧頂一個或兩個數值並將複製值或雙份的複製值從新壓入棧頂:dup、dup二、
dup_x一、dup2_x一、dup_x二、dup2_x2。
將棧最頂端的兩個數值互換:swap。
控制轉移指令可讓 Java 虛擬機有條件或無條件地從指定的位置指令而不是控制轉移指
令的下一條指令繼續執行程序,從概念模型上理解,能夠認爲控制轉移指令就是在有條件
或無條件地修改 PC 寄存器的值。控制轉移指令以下。
條件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、
if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq 和 if_acmpne。
複合條件分支:tableswitch、lookupswitch。
無條件分支:goto、goto_w、jsr、jsr_w、ret。
invokevirtual 指令用於調用對象的實例方法,根據對象的實際類型進行分派(虛方法分
派),這也是 Java 語言中最多見的方法分派方式。
invokeinterface 指令用於調用接口方法,它會在運行時搜索一個實現了這個接口方法的對
象,找出適合的方法進行調用。
invokespecial 指令用於調用一些須要特殊處理的實例方法,包括實例初始化方法、私有方
法和父類方法。
invokestatic 指令用於調用類方法(static 方法)。
invokedynamic 指令用於在運行時動態解析出調用點限定符所引用的方法,並執行該方
法,前面 4 條調用指令的分派邏輯都固化在 Java 虛擬機內部,而 invokedynamic 指令的
分派邏輯是由用戶所設定的引導方法決定的。
方法調用指令與數據類型無關。
是根據返回值的類型區分的,包括 ireturn(當返回值是 boolean、byte、char、short 和 int
類型時使用)、lreturn、freturn、dreturn 和 areturn,另外還有一條 return 指令供聲明爲
void 的方法、實例初始化方法以及類和接口的類初始化方法使用。