原文:http://blog.csdn.NET/u010425776/article/details/51245055
什麼是JVM的“無關性”?
Java具有平臺無關性,也就是任何操作系統都能運行Java代碼。之所以能實現這一點,是因爲Java運行在虛擬機之上,不同的操作系統都擁有各自的Java虛擬機,因此Java能實現“一次編寫,處處運行”。
而JVM不僅具有平臺無關性,還具有語言無關性。
平臺無關性是指不同操作系統都有各自的JVM,而語言無關性是指Java虛擬機能運行除Java以外的代碼!
這聽起來非常驚人,但JVM對能運行的語言是有嚴格要求的。首先來了解下Java代碼的運行過程。
Java源代碼首先需要使用Javac編譯器編譯成class文件,然後啓動JVM執行class文件,從而程序開始運行。
也就是JVM只認識class文件,它並不管何種語言生成了class文件,只要class文件符合JVM的規範就能運行。
因此目前已經有Scala、JRuby、Jython等語言能夠在JVM上運行。它們有各自的語法規則,不過它們的編譯器都能將各自的源碼編譯成符合JVM規範的class文件,從而能夠藉助JVM運行它們。
縱觀Class文件結構
class文件是二進制文件,它的內容具有嚴格的規範,文件中沒有任何空格,全是連續的0/1。class文件中的所有內容被分爲兩種類型:無符號數 和 表。
- 無符號數
它表示class文件中的值,這些值沒有任何類型,但有不同的長度。根據這些值長度的不同分爲:u1、u2、u4、u8,分別代表1字節的無符號數、2字節的無符號數、4字節的無符號數、8字節的無符號數。
- 表
class文件中所有數據(即無符號數)要麼單獨存在,要麼由多個無符號數組成二維表。即class文件中的數據要麼是單個值,要麼是二維表。
class文件的組織結構
- 魔數
- 本文件的版本信息
- 常量池
- 訪問標誌
- 類索引
- 父類索引
- 接口索引集合
- 字段表集合
- 方法表集合
Class文件的構成1:魔數
class文件的頭4個字節稱爲魔數,用來表示這個class文件的類型。
魔數的作用就相當於文件後綴名,只不過後綴名容易被修改,不安全,因此在class文件中標示文件類型比較合適。
class文件的魔數是用16進製表示的“CAFEBABE”,非常具有浪漫主義色彩,誰說程序員的情商都很低!
Class文件的構成2:版本信息
緊接着魔數的4個字節是版本號。它表示本class中使用的是哪個版本的JDK。
在高版本的JVM上能夠運行低版本的class文件,但在低版本的JVM上無法運行高版本的class文件,即使該class文件中沒有用到任何高版本JDK的特性也無法運行!
Class文件的構成3:常量池
1. 什麼是常量池?
緊接着版本號之後的就是常量池。常量池中存放兩種類型的常量:
- 字面值常量
字面值常量即我們在程序中定義的字符串、被final修飾的值。 - 符號引用
符號引用就是我們定義的各種名字:- 類和接口的全限定名
- 字段的名字 和 描述符
- 方法的名字 和 描述符
2. 常量池的特點
- 常量池長度不固定
常量池的大小是不固定的,因此常量池開頭放置一個u2類型的無符號數,用來存儲當前常量池的容量。JVM根據這個值就知道常量池的頭尾來。
注:這個值是從1開始的,若爲5表示池中有4個常量。 - 常量池中的常量由而爲表來表示
常量池開頭有個常量池容量計數器,接下來就全是一個個常量了,只不過常量都是由一張張二維表構成,除了記錄常量的值以外,還記錄當前常量的相關信息。 - 常量池是class文件的資源倉庫
- 常量池是與本class中其它部分關聯最多的部分
- 常量池是class文件中空間佔用最大的部分之一
3. 常量池中常量的類型
剛纔介紹了,常量池中的常量大體上分爲:字面值常量 和 符號引用。在此基礎上,根據常量的數據類型不同,又可以被細分爲14種常量類型。這14種常量類型都有各自的二維表示結構。每種常量類型的頭1個字節都是tag,用於表示當前常量屬於14種類型中的哪一個。
以CONSTANT_Class_info常量爲例,它的二維表示結構如下:
CONSTANT_Class_info表:
類型 | 名稱 | 數量 |
---|---|---|
u1 | tag | 1 |
u2 | name_index | 1 |
tag表示當前常量的類型(當前常量爲CONSTANT_Class_info,因此tag的值應爲7,表示一個類或接口的全限定名);
name_index表示這個類或接口全限定名的位置。它的值表示指向常量池的第幾個常量。它會指向一個CONSTANT_Utf8_info類型的常量,它的二維表結構如下:
CONSTANT_Utf8_info表:
類型 | 名稱 | 數量 |
---|---|---|
u1 | tag | 1 |
u2 | length | 1 |
u1 | bytes | length |
CONSTANT_Utf8_info表示字符串常量;
tag表示當前常量的類型,這裏應該是1;
length表示這個字符串的長度;
bytes爲這個字符串的內容(採用縮略的UTF8編碼)
問:爲什麼Java中定義的類、變量名字必須小於64K?
類、接口、變量等名字都屬於符號引用,它們都存儲在常量池中。而不管哪種符號引用,它們的名字都由CONSTANT_Utf8_info類型的常量表示,這種類型的常量使用u2存儲字符串的長度。由於2字節最多能表示65535個數,因此這些名字的最大長度最多隻能是64K。
問:什麼是UTF-8編碼?什麼是縮略UTF-8編碼?
前者每個字符使用3個字節表示,而後者把128個ASKII碼用1字節表示,某些字符用2字節表示,某些字符用3字節表示。
Class文件的構成4:訪問標誌
在常量池之後是2字節的訪問標誌。訪問標誌是用來表示這個class文件是類還是接口、是否被public修飾、是否被abstract修飾、是否被final修飾等。
由於這些標誌都由是/否表示,因此可以用0/1表示。
訪問標誌爲2字節,可以表示16位標誌,但JVM目前只定義了8種,未定義的直接寫0.
Class文件的構成5:類索引、父類索引、接口索引集合
類索引、父類索引、接口索引集合是用來表示當前class文件所表示類的名字、父類名字、接口們的名字。
它們按照順序依次排列,類索引和父類索引各自使用一個u2類型的無符號常量,這個常量指向CONSTANT_Class_info類型的常量,該常量的bytes字段記錄了本類、父類的全限定名。
由於一個類的接口可能有好多個,因此需要用一個集合來表示接口索引,它在類索引和父類索引之後。這個集合頭兩個字節表示接口索引集合的長度,接下來就是接口的名字索引。
Class文件的構成6:字段表的集合
1. 什麼是字段表集合?
接下來是字段表的集合。字段表集合用於存儲本類所涉及到的成員變量,包括實例變量和類變量,但不包括方法中的局部變量。
每一個字段表只表示一個成員變量,本類中所有的成員變量構成了字段表集合。
2. 字段表結構的定義
類型 | 名稱 | 數量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
- access_flags
字段的訪問標誌。在Java中,每個成員變量都有一系列的修飾符,和上述class文件的訪問標誌的作用一樣,只不過成員變量的訪問標誌與類的訪問標誌稍有區別。 - name_index
本字段名字的索引。指向一個CONSTANT_Class_info類型的常量,這裏面存儲了本字段的名字等信息。 - descriptor_index
描述符。用於描述本字段在Java中的數據類型等信息(下面詳細介紹) - attributes_count
屬性表集合的長度。 - attributes
屬性表集合。到descriptor_index爲止是字段表的固定信息,光有上述信息可能無法完整地描述一個字段,因此用屬性表集合來存放額外的信息,比如一個字段的值。(下面會詳細介紹)
3. 什麼是描述符?
成員變量(包括靜態成員變量和實例變量) 和 方法都有各自的描述符。
對於字段而言,描述符用於描述字段的數據類型;
對於方法而言,描述符用於描述字段的數據類型、參數列表、返回值。
在描述符中,基本數據類型用大寫字母表示,對象類型用“L對象類型的全限定名”表示,數組用“[數組類型的全限定名”表示。
描述方法時,將參數根據上述規則放在()中,()右側按照上述方法放置返回值。而且,參數之間無需任何符號。
4. 字段表集合的注意點
- 一個class文件的字段表集合中不能出現從父類/接口繼承而來字段;
- 一個class文件的字段表集合中可能會出現程序猿沒有定義的字段
如編譯器會自動地在內部類的class文件的字段表集合中添加外部類對象的成員變量,供內部類訪問外部類。 - Java中只要兩個字段名字相同就無法通過編譯。但在JVM規範中,允許兩個字段的名字相同但描述符不同的情況,並且認爲它們是兩個不同的字段。
Class文件的構成7:方法表的集合
在class文件中,所有的方法以二維表的形式存儲,每張表來表示一個函數,一個類中的所有方法構成方法表的集合。
方法表的結構和字段表的結構一致,只不過訪問標誌和屬性表集合的可選項有所不同。
類型 | 名稱 | 數量 |
---|---|---|
u2 | access_flags | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
方法表的屬性表集合中有一張Code屬性表,用於存儲當前方法經編譯器編譯過後的字節碼指令。
方法表集合的注意點
- 如果本class沒有重寫父類的方法,那麼本class文件的方法表集合中是不會出現父類/父接口的方法表;
- 本class的方法表集合可能出現程序猿沒有定義的方法
編譯器在編譯時會在class文件的方法表集合中加入類構造器和實例構造器。 - 重載一個方法需要有相同的簡單名稱和不同的特徵簽名。JVM的特徵簽名和Java的特徵簽名有所不同:
- Java特徵簽名:方法參數在常量池中的字段符號引用的集合
- JVM特徵簽名:方法參數+返回值