談論 JVM 的無關性,主要有如下兩個:
java
Java 源代碼首先須要使用 Javac 編譯器編譯成 .class 文件,而後由 JVM 執行 .class 文件,從而程序開始運行。安全
JVM 只認識 .class 文件,它不關心是何種語言生成了 .class 文件,只要 .class 文件符合 JVM 的規範就能運行。
目前已經有 JRuby、Jython、Scala 等語言可以在 JVM 上運行。它們有各自的語法規則,不過它們的編譯器
都能將各自的源碼編譯成符合 JVM 規範的 .class 文件,從而可以藉助 JVM 運行它們。編碼
Java 語言中的各類變量、關鍵字和運算符號的語義最終都是由多條字節碼命令組合而成的,
所以字節碼命令所能提供的語義描述能力確定會比 Java 語言自己更增強大。
所以,有一些 Java 語言自己沒法有效支持的語言特性,不表明字節碼自己沒法有效支持。
Class 文件時二進制文件,它的內容具備嚴格的規範,文件中沒有任何空格,全都是連續的 0/1。Class 文件
中的全部內容被分爲兩種類型:無符號數、表。操作系統
無符號數表示 Class 文件中的值,這些值沒有任何類型,但有不一樣的長度。u一、u二、u四、u8 分別表明 1/2/4/8 字節的無符號數。對象
由多個無符號數或者其餘表做爲數據項構成的符合數據類型。繼承
Class 文件具體由如下幾個構成:索引
Class 文件的頭 4 個字節稱爲魔數,用來表示這個 Class 文件的類型。接口
Class 文件的魔數是用 16 進製表示的「CAFE BABE」,是否是很具備浪漫色彩?ip
魔數至關於文件後綴名,只不事後綴名容易被修改,不安全,所以在 Class 文件中標識文件類型比較合適。
緊接着魔數的 4 個字節是版本信息,5-6 字節表示次版本號,7-8 字節表示主版本號,它們表示當前 Class 文件中使用的是哪一個版本的 JDK。字符串
高版本的 JDK 能向下兼容之前版本的 Class 文件,但不能運行之後版本的 Class 文件,即時文件格式並未發生任何變化,虛擬機也必需拒絕執行超過其版本號的 Class 文件。
版本信息以後就是常量池,常量池中存放兩種類型的常量:
字面值常量就是咱們在程序中定義的字符串、被 final 修飾的值。
符號引用就是咱們定義的各類名字:類和接口的全限定名、字段的名字和描述符、方法的名字和描述符。
類型 | tag | 描述 |
---|---|---|
CONSTANT_utf8_info | 1 | UTF-8編碼的字符串 |
CONSTANT_Integer_info | 3 | 整型字面量 |
CONSTANT_Float_info | 4 | 浮點型字面量 |
CONSTANT_Long_info | 5 | 長整型字面量 |
CONSTANT_Double_info | 6 | 雙精度浮點型字面量 |
CONSTANT_Class_info | 7 | 類或接口的符號引用 |
CONSTANT_String_info | 8 | 字符串類型字面量 |
CONSTANT_Fieldref_info | 9 | 字段的符號引用 |
CONSTANT_Methodref_info | 10 | 類中方法的符號引用 |
CONSTANT_InterfaceMethodref_info | 11 | 接口中方法的符號引用 |
CONSTANT_NameAndType_info | 12 | 字段或方法的符號引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 標識方法類型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一個動態方法調用點 |
對於 CONSTANT_Class_info(此類型的常量表明一個類或者接口的符號引用),它的二維表結構以下:
類型 | 名稱 | 數量 |
---|---|---|
u1 | tag | 1 |
u2 | name_index | 1 |
tag 是標誌位,用於區分常量類型;name_index 是一個索引值,它指向常量池中一個 CONSTANT_Utf8_info 類型常量,此常量表明這個類(或接口)的全限定名,這裏 name_index 值若爲 0x0002,也便是指向了常量池中的第二項常量。
CONSTANT_Utf8_info 型常量的結構以下:
類型 | 名稱 | 數量 |
---|---|---|
u1 | tag | 1 |
u2 | length | 1 |
u1 | bytes | length |
tag 是當前常量的類型;length 表示這個字符串的長度;bytes 是這個字符串的內容(採用縮略的 UTF8 編碼)
在常量池結束以後,緊接着的兩個字節表明訪問標誌,這個標誌用於識別一些類或者接口層次的訪問信息,包括:這個 Class 是類仍是接口;是否認義爲 public 類型;是否被 abstract/final 修飾。
類索引和父類索引都是一個 u2 類型的數據,而接口索引集合是一組 u2 類型的數據的集合,Class 文件中由這三項數據來肯定類的繼承關係。類索引用於肯定這個類的全限定名,父類索引用於肯定這個類的父類的全限定名。
因爲 Java 不容許多重繼承,因此父類索引只有一個,除了 java.lang.Object 以外,全部的 Java 類都有父類,所以除了 java.lang.Object 外,全部 Java 類的父類索引都不爲 0。一個類可能實現了多個接口,所以用接口索引集合來描述。這個集合第一項爲 u2 類型的數據,表示索引表的容量,接下來就是接口的名字索引。
類索引和父類索引用兩個 u2 類型的索引值表示,它們各自指向一個類型爲 CONSTANT_Class_info 的類描述符常量,經過該常量總的索引值能夠找到定義在 CONSTANT_Utf8_info 類型的常量中的全限定名字符串。
字段表集合存儲本類涉及到的成員變量,包括實例變量和類變量,但不包括方法中的局部變量。
每個字段表只表示一個成員變量,本類中的全部成員變量構成了字段表集合。字段表結構以下:
類型 | 名稱 | 數量 | 說明 |
---|---|---|---|
u2 | access_flags | 1 | 字段的訪問標誌,與類稍有不一樣 |
u2 | name_index | 1 | 字段名字的索引 |
u2 | descriptor_index | 1 | 描述符,用於描述字段的數據類型。 基本數據類型用大寫字母表示; 對象類型用「L 對象類型的全限定名」表示。 |
u2 | attributes_count | 1 | 屬性表集合的長度 |
u2 | attributes | attributes_count | 屬性表集合,用於存放屬性的額外信息,如屬性的值。 |
字段表集合中不會出現從父類(或接口)中繼承而來的字段,但有可能出現本來 Java 代碼中不存在的字段,譬如在內部類中爲了保持對外部類的訪問性,會自動添加指向外部類實例的字段。
方法表結構與屬性表相似。
volatile 關鍵字 和 transient 關鍵字不能修飾方法,因此方法表的訪問標誌中沒有 ACC_VOLATILE 和 ACC_TRANSIENT 標誌。
方法表的屬性表集合中有一張 Code 屬性表,用於存儲當前方法經編譯器編譯後的字節碼指令。
每一個屬性對應一張屬性表,屬性表的結構以下:
類型 | 名稱 | 數量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |