Java虛擬機不和包括Java在內的任何語言綁定,它只與Class文件這種特定的二進制文件所關聯
,不管使用何種語言進行軟件開發,只要能將源文件編譯爲正確的Class文件,那麼這種語言就能夠這Java虛擬機上執行。前端
想要讓一個Java程序正確的運行在JVM中,Java源碼就必需要被編譯爲符合JVM規範的字節碼。java
詞法解析、語法解析、語義解析以及生成字節碼
前端編譯面試
Java源代碼的編譯結果是字節碼,那麼確定要有一種可以將Java源代碼編譯爲字節碼,承擔這個責任的就是一配置在path環境變量中的javac編譯器
。javac是一種可以將Java源碼編譯爲字節碼的前端編譯器
。後端
優勢:數組
缺點:安全
後端編譯/JIT編譯框架
經過Java虛擬機(JVM)內置的即時編譯器(Just In Time Compiler,JIT編譯器);在運行時把Class文件字節碼編譯成本地機器碼的過程。佈局
優勢:性能
缺點:優化
靜態提早編譯(AOT)
程序運行前,直接把Java源碼文件編譯成本地機器碼的過程。
優勢:
缺點:
目前Java體系中主要仍是採用前端編譯+JIT編譯的方式
運做過程:
面試題
首先在聲明Integer x的時候會調用Integer.valueOf方法,首先看下這個方法
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
若是所傳入的值是-128~127之間,就只用靜態內部類的cache數組,不然就new一個新的對象。
這也代表爲何 Integer x = 128; Integer y = 128; x != y 的緣由。
以後能夠看到調用了 Integer.intValue 進行自動拆箱操做,使得 x 變成 int 類型進行比較。
源代碼通過編譯器編譯以後便生成一個字節碼文件,字節碼是一種二進制的類文件,它的內容是JVM的指令,而不像C、C++經由編譯器直接生成機器碼
。
Java虛擬機的指令,由一個字節長度的、表明着某種特定操做含義的操做碼
(opcode),以及跟隨其後的零至多個表明此操做所需參數的操做數
(operand)所構成。虛擬機中許多指令並不包含操做數,只有一個操做碼。
好比 aload_1 (操做碼) 、aload 4 (操做碼 + 操做數),由於aload只有0、一、二、3因此若是想繼續擴充,就要用到操做數。
因爲一個Class文件都對應着惟一一個類或接口的定義信息,但反過來講,Class文件實際上它並不必定以磁盤文件形式存在。Class文件是一組以8位字節爲基礎單位的二進制流
。
Class文件格式
Class的結構不像 XML 等描述語言,因爲它沒有任何分割符號,因此這其中的數據項,不管是字節順序仍是數量,都是被嚴格限定的,哪一個字節表明什麼含義、長度多少、前後順序,都不容許改變。
Class文件格式採用一種相似於C語言結構體的方式進行數據存儲,這種結構中只有兩種數據類型:無符號數
和表
。
Class文件結構概述
Class文件的結構並非一成不變的,隨着Java虛擬機的不斷髮展,老是不可避免地會對Class文件結構作出一些調整,可是基本結構和框架是很是穩定的。
結構以下:
| 類型 | 名稱 | 說明 | 長度 | 數量 |
| -------------- | ------------------- | ---------------------- | ------- | --------------------- |
| u4 | magic | 魔數,識別Class文件格式 | 4個字節 | 1 |
| u2 | minor_version | 副版本號(小版本) | 2個字節 | 1 |
| u2 | major_version | 主版本號(大版本) | 2個字節 | 1 |
| u2 | constant_pool_count | 常量池計數器 | 2個字節 | 1 |
| cp_info | constant_pool | 常量池表 | n個字節 | constant_pool_count-1 |
| u2 | access_flags | 訪問標識 | 2個字節 | 1 |
| u2 | this_class | 類索引 | 2個字節 | 1 |
| u2 | super_class | 父類索引 | 2個字節 | 1 |
| u2 | interfaces_count | 接口計數器 | 2個字節 | 1 |
| u2 | interfaces | 接口索引集合 | 2個字節 | interfaces_count |
| u2 | fields_count | 字段計數器 | 2個字節 | 1 |
| field_info | fields | 字段表 | n個字節 | fields_count |
| u2 | methods_count | 方法計數器 | 2個字節 | 1 |
| method_info | methods | 方法表 | n個字節 | methods_count |
| u2 | attributes_count | 屬性計數器 | 2個字節 | 1 |
| attribute_info | attributes | 屬性表 | n個字節 | attributes_count |
首先咱們建立一個簡單的源碼:
public class Demo { private int num = 1; public int add() { num = num + 2; return num; } }
經過 javac 命令編譯後能夠看到Class文件內容:
public class Demo { private int num = 1; public Demo() { } public int add() { this.num += 2; return this.num; } }
爲何編譯之後增長了無參構造器
,以及this關鍵字
,咱們一點點分析。
以後咱們使用Notepad++
,須要安裝一個HEX-Editor
插件來打開這個Class文件就能夠看到下圖內容。
這裏就直接使用 excel 來清晰解釋具體內容。
使用魔數而不是擴展名來識別主要是基於安全方面考慮,由於文件擴展名是能夠隨意改變的。
| 主版本(十進制) | 副版本(十進制) | 編譯器版本 |
| ---------------- | ---------------- | ---------- |
| 45 | 3 | 1.1 |
| 46 | 0 | 1.2 |
| 47 | 0 | 1.3 |
| 48 | 0 | 1.4 |
| 49 | 0 | 1.5 |
| 50 | 0 | 1.6 |
| 51 | 0 | 1.7 |
| 52 | 0 | 1.8 |
| 53 | 0 | 1.9 |
| 54 | 0 | 1.10 |
| 55 | 0 | 1.11 |
java.lang.UnsupportedClassVersionError
異常。常量池表項
中,用於存放編譯期生成的各類字面量
和符號引用
,這部份內容將在類加載後進入方法區的運行時常量池
中存放。咱們看剛纔舉例的Demo:其值爲0x0016,轉換爲十進制也就是22。
可是實際中只有21項常量,範圍是1-21。
這裏的常量池把第0項空出來了,爲了知足後面某些指向常量池的索引值的數據在特定狀況下須要表達"不引用任何一個常量池項"的含義,這種狀況可使用索引0來表示。
字面量(Literal)
和符號引用(Symbolic Reference)
。常量池主要存放放兩大類變量:字面量(Literal)
和符號引用(Symbolic Reference)
。
常量 | 具體的常量 |
---|---|
字面量 | 文本字符串 |
聲明爲final的常量值 | |
符號引用 | 類和接口的全限定名 |
字段和名稱的描述符 | |
方法的名稱和描述符 |
全限定名
com/test/Demo這個就是類的全限定名,僅僅是把包名的"."替換成了"/",爲了使連續的多個全限定名之間不產生混淆,在使用時最後通常會加入一個";"表示全限定名結束。
簡單名稱
簡單名稱是指沒有類型和參數修飾的方法或者字段名稱,上面例子中的類的add()方法,和num字段的簡單名稱分別是add和num。
描述符
描述符的做用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。
根據描述符規則,基本數據類型(boolean,byte,char,short,int,float,long,double)以及表明無返回值的void類型都用一個大寫字符來表示,而對象類型則用字符L加對象的全限定名來表示:
標誌符 | 含義 |
---|---|
B | 基本數據類型byte |
C | 基本數據類型char |
D | 基本數據類型double |
F | 基本數據類型float |
I | 基本數據類型int |
J | 基本數據類型long |
S | 基本數據類型short |
Z | 基本數據類型boolean |
V | 表明void類型 |
L | 對象類型,好比:Ljava/lang/Object; |
[ | 數組類型,表明一維數組。好比:double[][][] = [[[D |
public static void main(String[] args) { Object[] arr = new Object[10]; System.out.println(arr);//[Ljava.lang.Object;@14ae5a5 Long[][] longs = new Long[10][10]; System.out.println(longs);//[[Ljava.lang.Long;@7f31245a int[][] ints = new int[10][10]; System.out.println(ints);//[[I@7f31245a }
須要注意的是,用描述符來描述方法的時候,先參數列表後返回值的順序描述,參數列表按照參數的嚴格順序放在一組小括號"()"內。
虛擬機在加載Class文件時纔會進行動態連接,也就是說,Class文件中不會保存各個方法和字段的最終內存佈局信息,所以,這些字段和方法的符號引用不通過轉換是沒法直接被虛擬機使用的。當虛擬機運行時,須要從常量池中得到對應的符號引用,再在類加載過程當中的解析階段將其替換爲直接引用,並翻譯到具體的內存地址中
。
一組符號
來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時無歧義地定位到目標便可。符號引用與虛擬機實現的內存佈局無關
,引用的目標並不必定已經加載到了內存中。指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是與虛擬機實現的內存佈局相關的
,同一個符號引用在不一樣虛擬機實例上翻譯出來通常不會相同。若是有了直接引用,則說明引用的目標一定存在內存之中。類型 | 標誌(或標識) | 描述 |
---|---|---|
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 | 表示一個動態方法調用點 |
從上面的圖中能夠看到,雖然每一項的結構都不相同,可是它們有個共同點,就是每一項的第一個字節都是一個標誌位,標識這一項是哪一種類型的常量。
在常量池後,緊接着訪問標記,該標記使用兩個字節表示,用於識別一些類或者接口層次的訪問信息,包括:這個Class是類仍是接口;是否認義爲 public 類型;是否認義爲 abstract 類型;若是是類的話,是否被聲明爲 final 等,詳細說明以下:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 標誌爲public類型 |
ACC_FINAL | 0x0010 | 標誌被聲明爲final,只有類能夠設置 |
ACC_SUPER | 0x0020 | 標誌容許使用invokespecial字節碼指令的新語義,JDK1.0.2以後編譯出來的類的這個標誌默認爲真。(使用加強的方法調用父類方法) |
ACC_INTERFACE | 0x0200 | 標誌這是一個接口 |
ACC_ABSTRACT | 0x0400 | 是否爲abstract類型,對於接口或者抽象類來講,次標誌值爲真,其餘類型爲假 |
ACC_SYNTHETIC | 0x1000 | 標誌此類並不是由用戶代碼產生(即:由編譯器產生的類,沒有源碼對應) |
ACC_ANNOTATION | 0x2000 | 標誌這是一個註解 |
ACC_ENUM | 0x4000 | 標誌這是一個枚舉 |
咱們能夠看到上面的Demo的字節碼對應的訪問標記是21,也就是對應表格中的 ACC_PUBLIC 和 ACC_SUPER 加起來就等於21。
長度 | 含義 |
---|---|
u2 | this_class |
u2 | super_class |
u2 | interfaces_count |
u2 | interfaces[interfaces_count] |
this_class(類索引)
2 字節無符號整數,指向常量池的索引。它提供了類的全限定名,如com/test/Demo。this_class的值必須是常量池中某項的一個有效索引值。常量池在這個索引出的成員必須爲constant_class_info類型結構體,該結構表示這個Class文件所定義的類或者接口。
super_class(父類索引)
interfaces
interfaces_count(接口計數器)
interfaces_count 項的值表示當前類或者接口的直接超接口數量。
interfaces[](接口索引集合)
interfaces[]中每一個成員的值必須是對常量池表中某項索引的有效索引值,它的長度爲interfaces_count。每一個成員interfaces[i]必須爲constant_class_info結構,其中 0 <= i < interfaces_count。在interfaces[]中,各成員所表示的接口順序對應的源代碼中給定的接口順序(從左至右)同樣,即 interface[0]對應的是源代碼中最左邊的接口。
類級變量以及實例變量
,可是不包括方法內部、代碼塊內部聲明的局部變量。字段的標識符、訪問修飾符(public、private或protected)、是類變量仍是實例變量(static修飾符)、是不是常量(final修飾符)
等。注意:
fields_count(字段計數器)
fields_count的值表示當前Class文件fields表的成員個數,用2個字節表示。
fields表中每一個成員都是一個field_info結構,用於表示該類或接口所聲明的全部字段或者實例字段,不包括方法內部聲明的變量,也不包括從父類或父接口繼承的那些字段。
fields[](字段表)
一個字段的信息包括以下這些信息。這些信息中,各個修飾符都是布爾值,要麼有,要麼沒有。
類型 | 名稱 | 含義 | 數量 |
---|---|---|---|
u2 | access_flags | 訪問標誌 | 1 |
u2 | name_index | 字段名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 屬性計數器 | 1 |
attribute_info | attributes | 屬性集合 | attributes_count |
字段表訪問標識
咱們知道,一個字段能夠被各類關鍵字修飾,好比做用域修飾符、static修飾符、final修飾符、volatile修飾符等等。字段訪問標誌有以下這些:
標誌名稱 | 標誌值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否爲public |
ACC_PRIVATE | 0x0002 | 字段是否爲private |
ACC_PROTECTED | 0x0004 | 字段是否爲protected |
ACC_STATIC | 0x0008 | 字段是否爲static |
ACC_FINAL | 0x0010 | 字段是否爲final |
ACC_VOLATILE | 0x0040 | 字段是否爲volatile |
ACC_TRANSTENT | 0x0080 | 字段是否爲transient |
ACC_SYNCHETIC | 0x1000 | 字段是否爲由編譯器自動產生 |
ACC_ENUM | 0x4000 | 字段是否爲enum |
字段名索引
根據字段名索引的值,查詢常量池中的指定索引項便可。
描述符索引
描述符的做用是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。根據描述符規則,基本數據類型及無返回值的void都是用大寫符來表示,對象使用字符L+全限定名錶示。
屬性集合
一個字段還可能擁有一些屬性,用於存儲更多的額外信息。好比初始化值、一些註釋信息等。屬性個數存放在attribute_count中,屬性具體內容存在attributes數組中。
結構爲:
ConstantValue_attribute{ u2 attribute_name_index; u4 attribute_length; u2 constantvalue_index; }
對於常量屬性而言,attribute_length的值恆爲2。
常量值索引所指向的 #8 其實就是對應int的值。
methods:指向常量池索引集合,它完整描述了每一個方法的簽名。
每個method_info項都對應着一個類或者接口中的方法信息
。好比方法的訪問修飾符,方法的返回值類型,以及方法的參數信息等。<clinit>()
和實例初始化方法<init>()
。methodds_count(方法計數器)
methods_count的值表示當前class文件methods表的成員個數。使用 2 個字節來表示。
methods表中每一個成員都是一個method_info結構。
methods[](方法表)
類型 | 名稱 | 含義 | 數量 |
---|---|---|---|
u2 | access_flags | 訪問標誌 | 1 |
u2 | name_index | 字段名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 屬性計數器 | 1 |
attribute_info | attributes | 屬性集合 | attributes_count |
方法表集合以後的屬性表集合,指的是class文件所攜帶的輔助信息
,好比該Class文件的源文件的名稱,以及任何帶有RetentionPolicy.CLASS或者RetentionPolicy.RUNTIME的註解。這類信息一般被用於Java虛擬機的驗證和運行,以及Java程序的調試。
此外,字段表、方法表均可以有本身的屬性表。用於描述某些場景專有的信息。
屬性表集合的限制沒有那麼嚴格,再也不要求各個屬性表具備嚴格的順序,而且只要不與已有的屬性名重複,任何人實現的編譯器均可以向屬性表中寫入本身定義的屬性信息,但Java虛擬機運行時會忽略掉它不認識的屬性。
屬性的通用格式
屬性表的結構比較靈活,各類不一樣的屬性只要知足如下結構便可:
類型 | 名稱 | 數量 | 含義 |
---|---|---|---|
u2 | attribute_name_index | 1 | 屬性名索引 |
u4 | attribute_length | 1 | 屬性長度 |
u1 | info | attribute_length | 屬性表 |
屬性類型
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節碼指令 |
ConstantValue | 字段表 | final關鍵字定義的常量池 |
Deprecated | 類,方法,字段表 | 被聲明爲deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
EnclosingMethod | 類文件 | 僅當一個類爲局部類或者匿名類是才能擁有這個屬性,這個屬性用於標識這個類所在的外圍方法 |
InnerClass | 類文件 | 內部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節碼指令的對應關係 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
StackMapTable | Code屬性 | JDK1.6中新增的屬性,供新的類型檢查檢驗器檢查和處理目標方法的局部變量和操做數有所須要的類是否匹配 |
Signature | 類,方法表,字段表 | 用於支持泛型狀況下的方法簽名 |
SourceFile | 類文件 | 記錄源文件名稱 |
SourceDebugExtension | 類文件 | 用於存儲額外的調試信息 |
Synthetic | 類,方法表,字段表 | 標誌方法或字段爲編譯器自動生成的 |
LocalVariableTypeTable | 類 | 使用特徵簽名代替描述符,是爲了引入泛型語法以後能描述泛型參數化類型而添加 |
RuntimeVisibleAnnotations | 類,方法表,字段表 | 爲動態註解提供支持 |
RuntimeInvisibleAnnotations | 表,方法表,字段表 | 用於指明哪些註解是運行時不可見的 |
RuntimeVisibleParameterAnnotation | 方法表 | 做用與RuntimeVisibleAnnotations屬性相似,只不過做用對象爲方法 |
RuntimeInvisibleParameterAnnotation | 方法表 | 做用與RuntimeInvisibleAnnotations屬性相似,做用對象哪一個爲方法參數 |
AnnotationDefault | 方法表 | 用於記錄註解類元素的默認值 |
BootstrapMethods | 類文件 | 用於保存invokeddynamic指令引用的引導方式限定符 |
Code屬性
Code屬性就是存放方法體裏面的代碼,像接口或者抽象方法
,沒有具體的方法體,所以也就不會有Code屬性了。
Code屬性表的結構:
類型 | 名稱 | 數量 | 含義 |
---|---|---|---|
u2 | attribute_name_index | 1 | 屬性名索引 |
u4 | attribute_length | 1 | 屬性長度 |
u2 | max_stack | 1 | 操做數棧深度的最大值 |
u2 | max_locals | 1 | 局部變量表所需的存續空間 |
u4 | code_length | 1 | 字節碼指令的長度 |
u1 | code | code_length | 存儲字節碼指令 |
u2 | exception_table_length | 1 | 異常表長度 |
exception_info | exception_table | exception_length | 異常表 |
u2 | attributes_count | 1 | 屬性集合計數器 |
attribute_info | attributes | attributes_count | 屬性集合 |
LineNumberTable屬性
源碼行號
與字節碼行號
之間的對應關係。LineNumberTable屬性表結構
LineNumberTable_attribute { u2 attribute_name_index; u4 attribute_length; u2 line_number_table_length; { u2 start_pc; u2 line_number; } line_number_table[line_number_table_length]; }
類型 | 名稱 | 數量 | 含義 |
---|---|---|---|
u2 | attribute_name_index | 1 | 屬性名索引 |
u4 | attribute_length | 1 | 屬性長度 |
u2 | line_number_table_length | 1 | 行號表長度 |
line_number_info | line_number_table | line_number_table_length | 行號表 |
SourceFile屬性
類型 | 名稱 | 數量 | 含義 |
---|---|---|---|
u2 | attribute_name_index | 1 | 屬性名索引 |
u4 | attribute_length | 1 | 屬性長度 |
u2 | sourcefile_index | 1 | 源碼文件索引 |
其長度老是固定的8個字節。
咱們看以前的Excel最後一位也能夠看到對應的就是源文件名。