您好,登錄后才能下訂單哦!
Java虛擬機不和包括Java在內的任何語言綁定,只與 "Class文件" 這種特定的二進制文件所關聯, Class文件中包含了Java虛擬機 指令集合符號表 以及若干其它輔助信息。 Java虛擬機作為一個通用的、機器無關的執行平臺,任何其他語言都可以將其作為語言的產品交付媒介。
Class文件是一組以8位字節為基礎的 二進制流 , 各個數據項目嚴格按照順序緊湊地排列在Class文件之中,中間沒有添加任何分隔符, 這使得整個Class文件中存儲的內容幾乎全部是程序運行的必要數據,沒有空隙存在。 當遇到需要占用8位字節以上空間的數據項時,則會按照 高位在前 的方式分割成若干個8位字節進行存儲。
Class文件格式采用一種類似于C語言結構體的偽結構來存儲數,這種偽結構有兩種數據類型:
無符號數
表
無符號數:屬于基本數據類型,以u1、u2、u4、u8來代表1個字節、2個字節、4個字節、8個字節的無符號數, 無符號數可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字符串值。
表:由多個無符號數或者其他表作為數據項構成的復合數據類型,所有表都習慣性地以"_info"結尾。 表用于描述有層次關系的復合結構的數據,整個Class文件本質上就是一張表。
每個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文件格式的改進,都集中在向 訪問標志 、 屬性表 這些在設計上就可擴展的數據結構中添加內容。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。