目錄
類文件結構
一 概述
在 Java 中,JVM 可以理解的代碼就叫做字節碼
(即擴展名爲 .class
的文件),它不面向任何特定的處理器,只面向虛擬機。Java 語言通過字節碼的方式,在一定程度上解決了傳統解釋型語言執行效率低的問題,同時又保留了解釋型語言可移植的特點。所以 Java 程序運行時比較高效,而且,由於字節碼並不針對一種特定的機器,因此,Java 程序無須重新編譯便可在多種不同操作系統的計算機上運行。
Clojure(Lisp 語言的一種方言)、Groovy、Scala 等語言都是運行在 Java 虛擬機之上。下圖展示了不同的語言被不同的編譯器編譯成.class
文件最終運行在 Java 虛擬機之上。.class
文件的二進制格式可以使用 WinHex 查看。
可以說.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];//屬性表集合
}
下面詳細介紹一下 Class 文件結構涉及到的一些組件。
Class文件字節碼結構組織示意圖 (之前在網上保存的,非常不錯,原出處不明):
2.1 魔數
u4 magic; //Class 文件的標誌
每個 Class 文件的頭四個字節稱爲魔數(Magic Number),它的唯一作用是確定這個文件是否爲一個能被虛擬機接收的 Class 文件。
程序設計者很多時候都喜歡用一些特殊的數字表示固定的文件類型或者其它特殊的含義。
2.2 Class 文件版本
u2 minor_version;//Class 的小版本號
u2 major_version;//Class 的大版本號
緊接着魔數的四個字節存儲的是 Class 文件的版本號:第五和第六是次版本號,第七和第八是主版本號。
高版本的 Java 虛擬機可以執行低版本編譯器生成的 Class 文件,但是低版本的 Java 虛擬機不能執行高版本編譯器生成的 Class 文件。所以,我們在實際開發的時候要確保開發的的 JDK 版本和生產環境的 JDK 版本保持一致。
2.3 常量池
u2 constant_pool_count;//常量池的數量
cp_info constant_pool[constant_pool_count-1];//常量池
緊接着主次版本號之後的是常量池,常量池的數量是 constant_pool_count-1(常量池計數器是從1開始計數的,將第0項常量空出來是有特殊考慮的,索引值爲0代表“不引用任何一個常量池項”)。
常量池主要存放兩大常量:字面量和符號引用。字面量比較接近於 Java 語言層面的的常量概念,如文本字符串、聲明爲 final 的常量值等。而符號引用則屬於編譯原理方面的概念。包括下面三類常量:
-
類和接口的全限定名
-
字段的名稱和描述符
-
方法的名稱和描述符
常量池中每一項常量都是一個表,這14種表有一個共同的特點:開始的第一位是一個 u1 類型的標誌位 -tag 來標識常量的類型,代表當前這個常量屬於哪種常量類型.
類型 | 標誌(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_MothodType_info | 16 | 標誌方法類型 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_InvokeDynamic_info | 18 | 表示一個動態方法調用點 |
.class
文件可以通過javap -v class類名
指令來看一下其常量池中的信息(javap -v class類名-> temp.txt
:將結果輸出到 temp.txt 文件)。
2.4 訪問標誌
在常量池結束之後,緊接着的兩個字節代表訪問標誌,這個標誌用於識別一些類或者接口層次的訪問信息,包括:這個 Class 是類還是接口,是否爲 public 或者 abstract 類型,如果是類的話是否聲明爲 final 等等。
類訪問和屬性修飾符:
我們定義了一個 Employee 類
package top.snailclimb.bean;
public class Employee {
...
}
通過javap -v class類名
指令來看一下類的訪問標誌。
2.5 當前類索引,父類索引與接口索引集合
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.6 字段表集合
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 的取值:
2.7 方法表集合
u2 methods_count;//Class 文件的方法的數量
method_info methods[methods_count];//一個類可以有個多個方法
methods_count 表示方法的數量,而 method_info 表示的方法表。
Class 文件存儲格式中對方法的描述與對字段的描述幾乎採用了完全一致的方式。方法表的結構如同字段表一樣,依次包括了訪問標誌、名稱索引、描述符索引、屬性表集合幾項。
method_info(方法表的) 結構:
方法表的 access_flag 取值:
注意:因爲volatile
修飾符和transient
修飾符不可以修飾方法,所以方法表的訪問標誌中沒有這兩個對應的標誌,但是增加了synchronized
、native
、abstract
等關鍵字修飾方法,所以也就多了這些關鍵字對應的標誌。
2.8 屬性表集合
u2 attributes_count;//此類的屬性表中的屬性數
attribute_info attributes[attributes_count];//屬性表集合
在 Class 文件,字段表,方法表中都可以攜帶自己的屬性表集合,以用於描述某些場景專有的信息。與 Class 文件中其它的數據項目要求的順序、長度和內容不同,屬性表集合的限制稍微寬鬆一些,不再要求各個屬性表具有嚴格的順序,並且只要不與已有的屬性名重複,任何人實現的編譯器都可以向屬性表中寫 入自己定義的屬性信息,Java 虛擬機運行時會忽略掉它不認識的屬性。