一.簡介
實作語言無關性的基礎仍然是虛拟機和位元組碼存儲格式。Java虛拟機不和包括Java在内任何語言綁定,它隻與Class檔案這種特定的二進制檔案格式所關聯,Class檔案中包含了Java虛拟機指令集和符号表以及若幹其他輔助資訊。
Clojure(Lisp 語言的一種方言)、Groovy、Scala 等語言都是運作在 Java 虛拟機之上。下圖展示了不同的語言被不同的編譯器編譯成.class檔案最終運作在 Java 虛拟機之上。

可以說.class檔案是不同的語言在 Java 虛拟機之間的重要橋梁,同時也是支援 Java 跨平台很重要的一個原因。
二.Class檔案結構
根據Java虛拟機規範,類檔案由單個ClassFile結構組成:
ClassFile{
u4 magic; //Class 檔案的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的數量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的通路标記
u2 this_class;//目前類
u2 super_class;//父類
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一個類可以實作多個接口
u2 fields_count;//Class 檔案的字段屬性
field_info fields[fields_count];//一個類會可以有個字段
u2 methods_count;//Class 檔案的方法數量
method_info methods[methods_count];//一個類可以有個多個方法
u2 attributes_count;//此類的屬性表中的屬性數
attribute_info attributes[attributes_count];//屬性表集合
}
根據Java虛拟機規範規定,Class檔案格式采用一種類似C語言結構體的僞結構來存儲資料,這種僞結構中隻有兩種資料類型:無符号和表
- 無符号數屬于基本的資料類型,以u1、u2、u4、u8來分别代表1個位元組、2個位元組、4個位元組和8個位元組的無符号數,無符号可以用來描述數字、索引引用、數量值或者按照UTF-8編碼構成字元串值
- 表是由多個無符号數或者其他表作為資料項構成的複合資料,所有表都習慣地以"_info"結尾。表用于描述有層次關系的複合結構的資料,整個Class檔案本質就是一張表。
Class檔案具體由以下幾個構成:
- 魔數
- 常量池
- 通路标志
- 類索引、父類索引、接口索引集合
- 字段表集合
- 方法表集合
- 屬性表集合
2.1 魔數
- Class檔案的頭4個位元組,唯一作用是确定檔案是否為一個可被虛拟機接受Class檔案,固定為“0xCAFEBABE”。
- 第5和第6個位元組是次版本号,第7和第8位元組是主版本号(0x0034為52,對應JDK版本1.8)Java的版本号是從45開始的,JDK1.1之後的每一個JDK大版本釋出主版本号向上加1,高版本的JDK能向下相容低版本的JDK。
2.2 常量池
上述圖中位元組碼,版本号後面就是常量池,常量池可以了解為class檔案資源倉庫,它是class檔案結構中與其它項目關聯最多的資料類型,也是占用class檔案空間最大的資料項目之一,也是class檔案結構中與其它項目關聯最多的資料類型,也是占用class檔案空間最大的資料項目之一,也是class檔案中第一出現表類型資料項目。
由于常量池中常量的數量不是固定的,是以常量池入口需要放置一項u2類型的資料,代表常量池的容量計數。不過,這裡需要注意的是,這個容器計數是從1開始的而不是從0開始,也就是說,常量池中常量的個數是這個容器計數-1。将0空出來的目的是滿足後面某些指向常量池的索引值的資料在特定情況下需要表達“不引用任何一個常量池項目”的含義。class檔案中隻有常量池的容量計數是從1開始的,對于其它集合類型,比如接口索引集合、字段表集合、方法表集合等的容量計數都是從0開始的。
常量池中主要存放兩大類常量:字面量和符号引用。字面量比較接近Java語言常量,如文本字元串,final常量等,而符号引用則屬于編譯原理方面的概念,包括以下三種:
- 類和接口的全限定名(Fully Qualified Name)
- 字段的名稱和描述符号(Descriptor)
- 方法的名稱和描述符
不同于C/C++, JVM是在加載Class檔案的時候才進行的動态連結,也就是說這些字段和方法符号引用隻有在運作期轉換後才能獲得真正的記憶體入口位址。當虛拟機運作時,需要從常量池獲得對應的符号引用,再在類建立或運作時解析并翻譯到具體的記憶體位址中。
類型 | 名稱 | 解釋 | 數量 |
---|---|---|---|
u4 | magic | 1 | |
u2 | minjor_version | 次版本号 | |
majior_version | 主版本号 | ||
constant_pool_count | 常量池常量個數 | ||
cp_info | constant_pool | constant_pool_count-1 | |
access_flags | 通路标記 | ||
this_class | 類索引 | ||
super_class | 父類索引 |
常量池表結構。
表結構又有不同的資料結構。
2.3 通路标志
常量池結束後緊接着的兩個位元組代表通路标志,用來辨別一些類或接口的通路資訊,包括:這個Class是類還是接口;是否定義為public;是否定義為abstract;如果是類的話,是否被聲明為final等。具體的标志位以及含義如下表:
2.4 目前類索引,父類索引與接口索引集合
u2 this_class;//目前類
u2 super_class;//父類
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一個類可以實作多個接口
類索引用于确定這個類的全限定名,父類索引用于确定這個類的父類的全限定名,由于 Java 語言的單繼承,是以父類索引隻有一個,除了 java.lang.Object 之外,所有的 java 類都有父類,是以除了 java.lang.Object 外,所有 Java 類的父類索引都不為 0。
接口索引集合用來描述這個類實作了那些接口,這些被實作的接口将按implents(如果這個類本身是接口的話則是extends) 後的接口順序從左到右排列在接口索引集合中。
2.5 字段表集合
u2 fields_count;//Class 檔案的字段的個數
field_info fields[fields_count];//一個類會可以有個字段
字段表(field info)用于描述接口或類中聲明的變量。字段包括類級變量以及執行個體變量,但不包括在方法内部聲明的局部變量。
field info(字段表) 的結構
access_flags: 字段的作用域(public ,private,protected修飾符),是執行個體變量還是類變量(static修飾符),可否被序列化(transient 修飾符),可變性(final),可見性(volatile 修飾符,是否強制從主記憶體讀寫)。
name_index: 對常量池的引用,表示的字段的名稱;
descriptor_index: 對常量池的引用,表示字段和方法的描述符;
attributes_count: 一個字段還會擁有一些額外的屬性,attributes_count 存放屬性的個數;
attributes[attributes_count]: 存放具體屬性具體内容。
字段的 access_flags 的取值
| 标志名稱 | 标志值 | 含義 |
|:----????:----????:----????:----????:----????:----????
| ACC_PUBLIC | 0x00 01 | 字段是否為public |
| ACC_PRIVATE | 0x00 02 | 字段是否為private |
| ACC_PROTECTED | 0x00 04 | 字段是否為protected |
| ACC_STATIC | 0x00 08 | 字段是否為static |
| ACC_FINAL | 0x00 10 | 字段是否為final |
| ACC_VOLATILE | 0x00 40 | 字段是否為volatile |
| ACC_TRANSTENT | 0x00 80 | 字段是否為transient |
| ACC_SYNCHETIC | 0x10 00 | 字段是否為由編譯器自動産生 |
| ACC_ENUM | 0x40 00 | 字段是否為enum |
2.6 方法表集合
u2 methods_count;//Class 檔案的方法的數量
method_info methods[methods_count];//一個類可以有個多個方法
methods_count 表示方法的數量,而 method_info 表示的方法表。
Class 檔案存儲格式中對方法的描述與對字段的描述幾乎采用了完全一緻的方式。方法表的結構如同字段表一樣,依次包括了通路标志、名稱索引、描述符索引、屬性表集合幾項。
method_info(方法表的) 結構
類檔案結構
标志名稱 | 标志值 | 含義 |
---|---|---|
ACC_PUBLIC | 0x00 01 | 方法是否為public |
ACC_PRIVATE | 0x00 02 | 方法是否為private |
ACC_PROTECTED | 0x00 04 | 方法是否為protected |
ACC_STATIC | 0x00 08 | 方法是否為static |
ACC_FINAL | 0x00 10 | 方法是否為final |
ACC_SYHCHRONRIZED | 0x00 20 | 方法是否為synchronized |
ACC_BRIDGE | 0x00 40 | 方法是否是有編譯器産生的方法 |
ACC_VARARGS | 0x00 80 | 方法是否接受參數 |
ACC_NATIVE | 0x01 00 | 方法是否為native |
ACC_ABSTRACT | 0x04 00 | 方法是否為abstract |
ACC_STRICTFP | 0x08 00 | 方法是否為strictfp |
ACC_SYNTHETIC | 0x10 00 | 方法是否是有編譯器自動産生的 |
方法裡的Java代碼,經過編譯器編譯成位元組碼指令後,存放在方法屬性表集合中一個名為"Code"的屬性裡面,屬性表作為calss檔案格式中最具擴充的一種資料項目.
2.7 屬性表集合
u2 attributes_count;//此類的屬性表中的屬性數
attribute_info attributes[attributes_count];//屬性表集合